├── pharmd-app
├── .prettierrc
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ └── manifest.json
├── src
│ ├── styles
│ │ ├── README.md
│ │ └── App.css
│ ├── assets
│ │ ├── images
│ │ │ └── mountains.jpg
│ │ └── icons
│ │ │ ├── errorFilled.svg
│ │ │ ├── verticalSplit.svg
│ │ │ ├── test.svg
│ │ │ ├── diagram.svg
│ │ │ ├── filter.svg
│ │ │ ├── help.svg
│ │ │ ├── student.svg
│ │ │ └── person.svg
│ ├── components
│ │ ├── README.md
│ │ ├── Form
│ │ │ ├── Button.js
│ │ │ ├── index.js
│ │ │ ├── FileSelector.js
│ │ │ └── Select.js
│ │ ├── Fields
│ │ │ ├── AvatarField.js
│ │ │ ├── EmphasisField.js
│ │ │ ├── TextField.js
│ │ │ ├── TemporaryField.js
│ │ │ ├── CohortField.js
│ │ │ ├── ChipField.js
│ │ │ ├── ScoredListField
│ │ │ │ ├── ScoredListField.js
│ │ │ │ ├── ScoredListItemSkeleton.js
│ │ │ │ ├── ScoredListItemError.js
│ │ │ │ └── ScoredListItem.js
│ │ │ ├── QuickChipField.js
│ │ │ ├── CourseField.js
│ │ │ ├── GradeField.js
│ │ │ ├── QuickInfoField.js
│ │ │ ├── NoteListField.js
│ │ │ ├── CourseListField.js
│ │ │ ├── QuickProfileField.js
│ │ │ └── NoteField.js
│ │ ├── Basic
│ │ │ ├── Radio Controls
│ │ │ │ ├── RadioButton.js
│ │ │ │ └── RadioButtonGroup.js
│ │ │ ├── IconButton.js
│ │ │ ├── Temp.css
│ │ │ ├── IconInfo.js
│ │ │ ├── Checkbox Controls
│ │ │ │ ├── CheckboxButton.js
│ │ │ │ ├── CheckboxFilterButtonGroup.js
│ │ │ │ └── CheckboxButtonGroup.js
│ │ │ ├── QuickInfo.js
│ │ │ ├── FilterChip.js
│ │ │ ├── NoteIcon.js
│ │ │ ├── Avatar.js
│ │ │ ├── Tooltip.js
│ │ │ ├── Icon.js
│ │ │ ├── ExpansionPanel.js
│ │ │ ├── GridCard.js
│ │ │ ├── StatusBullet.js
│ │ │ ├── BarGraph.js
│ │ │ ├── RangeSlider.js
│ │ │ ├── Breadcrumb.js
│ │ │ └── Autocomplete.js
│ │ ├── Table
│ │ │ ├── Table.js
│ │ │ └── TableRow.js
│ │ ├── Nav
│ │ │ ├── NavIndicator.js
│ │ │ ├── AppBar.js
│ │ │ ├── NavItemSecondary.js
│ │ │ └── NavItemLink.js
│ │ ├── Inputs
│ │ │ ├── StudentSearchInput.js
│ │ │ ├── SearchInput.js
│ │ │ ├── GpaSliderInput.js
│ │ │ ├── StatusCheckboxInput.js
│ │ │ └── CohortMultiSelectInput.js
│ │ └── Layout
│ │ │ ├── DashboardLayout.js
│ │ │ └── Menu.js
│ ├── services
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── Auth
│ │ │ ├── isAuth.js
│ │ │ ├── getPermissions.js
│ │ │ ├── logout.js
│ │ │ ├── index.js
│ │ │ ├── authError.js
│ │ │ └── login.js
│ │ ├── Data
│ │ │ ├── CreateHooks.js
│ │ │ ├── httpClient.js
│ │ │ ├── index.js
│ │ │ ├── EditHooks.js
│ │ │ ├── DeleteHooks.js
│ │ │ ├── UpdateHooks.js
│ │ │ └── GetHooks.js
│ │ ├── Utility.js
│ │ ├── FileUploadService.js
│ │ └── StudentAssessmentService.js
│ ├── config
│ │ ├── README.md
│ │ ├── customRoutes.js
│ │ └── backendRoutes.js
│ ├── constants
│ │ ├── README.md
│ │ ├── text.js
│ │ ├── routes.js
│ │ ├── mappers.js
│ │ └── apiObjects.js
│ ├── screens
│ │ ├── courses
│ │ │ ├── index.js
│ │ │ ├── CourseList.js
│ │ │ ├── CourseResource.js
│ │ │ └── CourseFilter.js
│ │ ├── upload
│ │ │ ├── index.js
│ │ │ ├── UploadResource.js
│ │ │ └── components
│ │ │ │ ├── UploadFileChooser.js
│ │ │ │ └── SpreadsheetUploader.js
│ │ ├── student
│ │ │ ├── index.js
│ │ │ ├── StudentQuickView.js
│ │ │ ├── StudentList.js
│ │ │ ├── StudentToolbarFilter.js
│ │ │ ├── StudentContentGrid.js
│ │ │ ├── StudentResource.js
│ │ │ └── StudentDisplayFilters.js
│ │ ├── profile
│ │ │ ├── ProfileForm.js
│ │ │ ├── ProfilePage.js
│ │ │ ├── index.js
│ │ │ ├── ProfileEdit.js
│ │ │ ├── ProfileCreate.js
│ │ │ ├── ProfileShow.js
│ │ │ └── ProfileManage.js
│ │ └── studentDetails
│ │ │ ├── StudentDetailsSide.js
│ │ │ ├── StudentExamList.js
│ │ │ ├── StudentCourseList.js
│ │ │ ├── StudentDetailsScreen.js
│ │ │ └── StudentDetailsContentGrid.js
│ ├── setupTests.js
│ ├── stories
│ │ ├── fileSelector.stories.js
│ │ ├── Spreadsheet-Uploader.stories.js
│ │ ├── spreadsheet-uploader.stories.js
│ │ ├── README.md
│ │ ├── navBar.stories.js
│ │ ├── routerBreadcrumb.stories.js
│ │ ├── rangeSlider.stories.js
│ │ ├── selector.stories.js
│ │ ├── expansionPanel.stories.js
│ │ ├── radioGroup.stories.js
│ │ ├── scoredListField.stories.js
│ │ ├── studentSearchInput.stories.js
│ │ ├── tooltip.stories.js
│ │ ├── navItem.stories.js
│ │ ├── avatar.stories.js
│ │ ├── icon.stories.js
│ │ ├── tableCells.stories.js
│ │ └── table.stories.js
│ ├── themes
│ │ ├── selected-theme.js
│ │ ├── dark-theme.js
│ │ ├── light-theme.js
│ │ └── field-colors.js
│ ├── Dashboard.js
│ ├── typeDefs.js
│ ├── index.js
│ ├── twin.d.ts
│ ├── plugins
│ │ └── type-system
│ │ │ └── index.js
│ ├── App.js
│ └── index.html
├── .babelrc
├── .eslintrc.json
├── .env.test
├── .storybook
│ ├── addons.js
│ ├── main.js
│ ├── config.js
│ └── theme-provider.js
├── .env.production
├── .env.development
├── .env
├── babel-plugin-macros.config.js
├── .gitignore
├── webpack.config.js
├── package.json
└── README.md
└── README.md
/pharmd-app/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 96,
3 | "singleQuote": false
4 | }
5 |
--------------------------------------------------------------------------------
/pharmd-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/pharmd-app/src/styles/README.md:
--------------------------------------------------------------------------------
1 | # Styles
2 |
3 | Contains stylesheets used by various parts of the application
4 |
--------------------------------------------------------------------------------
/pharmd-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/pharmd-tracker/develop/pharmd-app/public/favicon.ico
--------------------------------------------------------------------------------
/pharmd-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/pharmd-tracker/develop/pharmd-app/public/logo192.png
--------------------------------------------------------------------------------
/pharmd-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/pharmd-tracker/develop/pharmd-app/public/logo512.png
--------------------------------------------------------------------------------
/pharmd-app/src/assets/images/mountains.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/pharmd-tracker/develop/pharmd-app/src/assets/images/mountains.jpg
--------------------------------------------------------------------------------
/pharmd-app/src/components/README.md:
--------------------------------------------------------------------------------
1 | # Components
2 |
3 | The modular pieces that get used throughout the app to make it render and function
4 |
--------------------------------------------------------------------------------
/pharmd-app/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": ["macros", "@babel/plugin-proposal-class-properties"]
4 | }
5 |
--------------------------------------------------------------------------------
/pharmd-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "prettier"],
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": ["error"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/README.md:
--------------------------------------------------------------------------------
1 | #Services
2 |
3 | These files contain advanced/core logic of the application, including that functions to communicate with the backend
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/config/README.md:
--------------------------------------------------------------------------------
1 | #Config
2 |
3 | These files contain values that establish the configuration of the app, including environment variables and paths to access network resources
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/constants/README.md:
--------------------------------------------------------------------------------
1 | # Constants
2 |
3 | These files will contain values used by multiple portions of the app, but which will not readily change or be retrieved from the server
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/courses/index.js:
--------------------------------------------------------------------------------
1 | import TestIcon from "../../assets/icons/test.svg";
2 | import CourseScreen from "./CourseResource";
3 |
4 | export default {
5 | list: CourseScreen,
6 | icon: TestIcon
7 | };
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/upload/index.js:
--------------------------------------------------------------------------------
1 | import DiagramIcon from "../../assets/icons/diagram.svg";
2 | import UploadScreen from "./UploadResource";
3 |
4 | export default {
5 | list: UploadScreen,
6 | icon: DiagramIcon
7 | };
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/index.js:
--------------------------------------------------------------------------------
1 | import StudentIcon from "../../assets/icons/student.svg";
2 | import StudentScreen from "./StudentResource";
3 |
4 | export default {
5 | list: StudentScreen,
6 | icon: StudentIcon
7 | };
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Form/Button.js:
--------------------------------------------------------------------------------
1 | import tw, { styled } from "twin.macro";
2 | import Button from "@material-ui/core/Button";
3 |
4 | const ButtonCustom = styled(Button)`
5 | ${tw`m-4`}
6 | `;
7 |
8 | export default ButtonCustom;
9 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/AvatarField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Avatar from "../Basic/Avatar";
3 |
4 | const AvatarField = ({ record = {}, source }) => ;
5 |
6 | export default AvatarField;
7 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Form/index.js:
--------------------------------------------------------------------------------
1 | import Radio from "@material-ui/core/Radio";
2 | import Select from "./Select";
3 | import FileSelector from "./FileSelector";
4 | import Button from "./Button";
5 |
6 | export { Select, FileSelector, Button, Radio };
7 |
--------------------------------------------------------------------------------
/pharmd-app/.env.test:
--------------------------------------------------------------------------------
1 | # THESE VALUES WILL BE USED DURING TESTING (npm test)
2 |
3 | API_URL=http://localhost:3000
4 | OLD_API_URL=https://student-db-remote.herokuapp.com
5 |
6 | AUTH_URL=http://localhost:3000/auth
7 | OLD_AUTH_URL=https://student-db-remote.herokuapp.com/login
8 |
--------------------------------------------------------------------------------
/pharmd-app/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-knobs/register";
2 | import "@storybook/addon-actions/register";
3 | import "@storybook/addon-links/register";
4 |
5 | import "storybook-addon-styled-component-theme/dist/register";
6 | import "storybook-addon-designs/register";
7 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Radio Controls/RadioButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Radio from "@material-ui/core/Radio";
3 |
4 | const RadioButton = props => {
5 | return ;
6 | };
7 |
8 | export default RadioButton;
9 |
--------------------------------------------------------------------------------
/pharmd-app/.env.production:
--------------------------------------------------------------------------------
1 | # THESE VALUES WILL BE USED IN PRODUCTION CODE (npm build)
2 |
3 | API_URL=http://localhost:3000
4 | OLD_API_URL=https://student-db-remote.herokuapp.com
5 |
6 | AUTH_URL=http://localhost:3000/auth
7 | OLD_AUTH_URL=https://student-db-remote.herokuapp.com/login
8 |
9 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/index.js:
--------------------------------------------------------------------------------
1 | import FileUploadService from "./FileUploadService";
2 | import AuthProvider from "./Auth";
3 | import DataProvider from "./Data";
4 |
5 | // This should be kept up-to-date as new service files are added
6 | export { FileUploadService, AuthProvider, DataProvider };
7 |
--------------------------------------------------------------------------------
/pharmd-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom/extend-expect";
6 |
--------------------------------------------------------------------------------
/pharmd-app/src/constants/text.js:
--------------------------------------------------------------------------------
1 | export const HOME_TITLE = "Home";
2 | export const STUDENT_TITLE = "Students";
3 | export const COURSES_TITLE = "Courses";
4 | export const UPLOAD_TITLE = "Upload";
5 | export const STUDENT_QUICKVIEW_TITLE = "Quickview";
6 | export const STUDENT_DETAILS_TITLE = "Details";
7 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/fileSelector.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FileSelectorComponent from "../components/Form/FileSelector";
3 |
4 | export default {
5 | title: "File Selector",
6 | component: File
7 | };
8 |
9 | export const Default = () => ;
10 |
--------------------------------------------------------------------------------
/pharmd-app/src/constants/routes.js:
--------------------------------------------------------------------------------
1 | export const HOME_MAIN = "/";
2 | export const STUDENT_DETAILS = "/students/:id/details";
3 | export const STUDENT_QUICKVIEW = "/students/:id";
4 | export const STUDENTS_MAIN = "/students";
5 | export const COURSES_MAIN = "/courses";
6 | export const UPLOAD_MAIN = "/upload";
7 |
--------------------------------------------------------------------------------
/pharmd-app/.env.development:
--------------------------------------------------------------------------------
1 | # THESE VALUES WILL BE USED IN ANYTHING NOT DEVELOPMENT, PRODUCTION, OR TESTING (FALLBACK)
2 |
3 | API_URL=http://localhost:3000
4 | OLD_API_URL=https://student-db-remote.herokuapp.com
5 |
6 | AUTH_URL=http://localhost:3000/auth
7 | OLD_AUTH_URL=https://student-db-remote.herokuapp.com/login
8 |
--------------------------------------------------------------------------------
/pharmd-app/.env:
--------------------------------------------------------------------------------
1 | # THESE ENVIRONMENT VARIABLES WILL ACT AS THE DEFAULT CASE IF NOT IN LOCAL, PRODUCTION, OR TEST RUNNING ENVIRONMENT
2 |
3 | API_URL=http://localhost:3000
4 | OLD_API_URL=https://student-db-remote.herokuapp.com
5 |
6 | AUTH_URL=http://localhost:3000/auth
7 | OLD_AUTH_URL=https://student-db-remote.herokuapp.com/login
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/Spreadsheet-Uploader.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SpreadsheetUploader from "../screens/upload/components/SpreadsheetUploader";
3 | export default {
4 | title: "Spreadsheet Uploader",
5 | component: SpreadsheetUploader
6 | };
7 |
8 | export const Default = () => ;
9 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/spreadsheet-uploader.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SpreadsheetUploader from "../screens/upload/components/SpreadsheetUploader";
3 | export default {
4 | title: "Spreadsheet Uploader",
5 | component: SpreadsheetUploader
6 | };
7 |
8 | export const Default = () => ;
9 |
--------------------------------------------------------------------------------
/pharmd-app/src/constants/mappers.js:
--------------------------------------------------------------------------------
1 | import { COURSES_TITLE, STUDENT_TITLE, UPLOAD_TITLE } from "./text";
2 | import { COURSES_MAIN, STUDENTS_MAIN, UPLOAD_MAIN } from "./routes";
3 |
4 | export const MAP_BREADCRUMB_NAME = {
5 | [STUDENTS_MAIN]: STUDENT_TITLE,
6 | [COURSES_MAIN]: COURSES_TITLE,
7 | [UPLOAD_MAIN]: UPLOAD_TITLE
8 | };
9 |
--------------------------------------------------------------------------------
/pharmd-app/src/config/customRoutes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route } from "react-router-dom";
3 |
4 | import StudentDetailsScreen from "../screens/studentDetails/StudentDetailsScreen";
5 | import { STUDENT_DETAILS } from "../constants/routes";
6 |
7 | export default [ ];
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/themes/selected-theme.js:
--------------------------------------------------------------------------------
1 | import createDarkTheme from "./dark-theme";
2 | import createLigthTheme from "./light-theme";
3 |
4 | function selectTheme(theme) {
5 | switch (theme) {
6 | case "dark":
7 | return createDarkTheme();
8 | default:
9 | return createLigthTheme();
10 | }
11 | }
12 |
13 | export default selectTheme;
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pharmd-tracker
2 |
3 | **To run the front end:**
4 |
5 | **In pharmd-app, run:**
6 |
7 | - npm install
8 | - npm start
9 |
10 | **Afterward**
11 |
12 | - Install CORS plugin
13 | - link to [chrome plugin](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en)
14 | - Turn it on by clicking on the C icon
15 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/errorFilled.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/EmphasisField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import TextField from "./TextField";
4 |
5 | const StyledField = styled(TextField)`
6 | ${tw`fontStyle-6 text-secondary font-semibold`}
7 | `;
8 |
9 | const EmphasisField = props => {
10 | return ;
11 | };
12 |
13 | export default EmphasisField;
14 |
--------------------------------------------------------------------------------
/pharmd-app/src/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Card from "@material-ui/core/Card";
3 | import CardContent from "@material-ui/core/CardContent";
4 | import CardHeader from "@material-ui/core/CardHeader";
5 |
6 | export default () => (
7 |
8 |
9 | Lorem ipsum sic dolor amet...
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/TextField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import { TextField as RATextField } from "react-admin";
4 |
5 | const StyledTextField = styled(RATextField)`
6 | ${tw`fontStyle-6 text-gray-700 font-medium`}
7 | `;
8 |
9 | const TextField = props => {
10 | return ;
11 | };
12 |
13 | export default TextField;
14 |
--------------------------------------------------------------------------------
/pharmd-app/babel-plugin-macros.config.js:
--------------------------------------------------------------------------------
1 | // babel-plugin-macros.config.js
2 | module.exports = {
3 | tailwind: {
4 | config: "./src/tailwind.config.js",
5 | styled: "styled-components/macro"
6 | },
7 | twin: {
8 | config: "./src/tailwind.config.js",
9 | preset: "styled-components",
10 | autoCssProp: true,
11 | debugProp: true,
12 | debugPlugins: false,
13 | debug: false,
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/README.md:
--------------------------------------------------------------------------------
1 | # Stories
2 |
3 | These files are [Storybook](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=2ahUKEwjJxpeg3-vnAhVKU98KHa_ZB2MQFjAAegQICRAC&url=https%3A%2F%2Fstorybook.js.org%2F&usg=AOvVaw3yFiehIItinYyB9pHN8YIy) stories used primarily for visual testing of the application's components. Can be tested by cloning the repo and running 'npm run storybook' in a terminal
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/isAuth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The place to validate the user's credentials
3 | * Redirecting to the login page whenever the REST response is 401 is usually not enough, so something else
4 | * should be done here to avoid using stale data
5 | * @return {Promise|Promise}
6 | */
7 | export default () => {
8 | return localStorage.getItem("token") ? Promise.resolve() : Promise.reject();
9 | };
10 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/IconButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import IconButtonMaterial from "@material-ui/core/IconButton";
3 | import Icon from "./Icon";
4 |
5 | const IconButton = ({ src, color = "tertiary" }) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default IconButton;
14 |
--------------------------------------------------------------------------------
/pharmd-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | ../.idea/*
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Temp.css:
--------------------------------------------------------------------------------
1 | .bar {
2 | animation: grow .25s cubic-bezier(0.42, 0, 1, 1);
3 | }
4 |
5 | .car {
6 | animation: grow .25s linear reverse;
7 | }
8 |
9 | @keyframes grow {
10 | 0% {
11 | height: 0%;
12 | }
13 | 100% {
14 | height: 100%;
15 | }
16 | }
17 |
18 | @keyframes shrink {
19 | 100% {
20 | height: 100%;
21 | }
22 | 0% {
23 | height: 0%;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/IconInfo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "./Icon";
3 | import Tooltip from "./Tooltip";
4 |
5 | const InfoIcon = ({ src, text, type = "error", size = "small" }) => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default InfoIcon;
16 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/getPermissions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Calls this method to check user permissions
3 | * Useful for enabling or disabling features on a per user basis
4 | * Called when user redirects
5 | * More info here: https://marmelab.com/react-admin/Authorization.html
6 | * @return {Promise|Promise}
7 | */
8 | export default () => {
9 | const role = localStorage.getItem("permissions");
10 | return role ? Promise.resolve(role) : Promise.reject();
11 | };
12 |
--------------------------------------------------------------------------------
/pharmd-app/src/typeDefs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {{
3 | * NUID: string,
4 | * courseName: string,
5 | * examName: string,
6 | * percentage: number,
7 | * studentName: string
8 | * courseTerm: string | null
9 | * }}
10 | * */
11 | export class BasicStudentAssessment {}
12 |
13 | /**
14 | * @type {{
15 | * data: Array,
16 | * headers: Array,
17 | * subHeaders: Array | null
18 | *
19 | * }}
20 | */
21 | export class FileData {}
22 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/verticalSplit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/navBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withKnobs } from "@storybook/addon-knobs";
3 | import { MemoryRouter } from "react-router";
4 | import AppBar from "../components/Nav/AppBar";
5 |
6 | export default {
7 | title: "AppBar",
8 | component: AppBar,
9 | decorators: [withKnobs]
10 | };
11 |
12 | export const Default = () => (
13 |
14 | } />
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/pharmd-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import * as serviceWorker from "./serviceWorker";
6 |
7 | ReactDOM.render( , document.getElementById("root"));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/pharmd-app/.storybook/main.js:
--------------------------------------------------------------------------------
1 | // Storybook is using the same webpack config file
2 | // we used for our app. THis allows us to use css
3 | // and scss for styling in storybook.
4 | const custom = require("../webpack.config");
5 |
6 | module.exports = {
7 | webpackFinal: config => {
8 | config.plugins.push(custom().plugins[2]);
9 | return {
10 | ...config,
11 | module: { ...config.module, rules: custom().module.rules }
12 | };
13 | },
14 | addons: ['@storybook/addon-actions/register']
15 | };
16 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/routerBreadcrumb.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withKnobs } from "@storybook/addon-knobs";
3 | import { MemoryRouter } from "react-router";
4 | import RouterBreadcrumb from "../components/Basic/Breadcrumb";
5 |
6 | export default {
7 | title: "Breadcrumb",
8 | component: RouterBreadcrumb,
9 | decorators: [withKnobs]
10 | };
11 |
12 | export const Default = () => (
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/rangeSlider.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import RangeSlider from "../components/Basic/RangeSlider";
3 |
4 | export default {
5 | title: "RangeSlider",
6 | component: RangeSlider
7 | };
8 |
9 | export const Default = () => (
10 |
11 | console.log("NEW VALUE", newVal)}
13 | setValueText={value => `${value}`}
14 | min={0}
15 | max={4}
16 | step={0.5}
17 | disabled={false}
18 | />
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/selector.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SelectorComponent from "../components/Form/Select";
3 |
4 | export default {
5 | title: "Selector",
6 | component: SelectorComponent
7 | };
8 |
9 | const selectData = [
10 | { value: ["3", "5"], displayValue: "3" },
11 | { value: ["3", "6"], displayValue: "4" },
12 | { value: ["3", "8"], displayValue: "5" }
13 | ];
14 |
15 | export const Default = () => (
16 | console.log(val)} />
17 | );
18 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/logout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Called when the user clicks the Logout button given by React Admin
3 | * Used often to remove potentially sensitive data from Redux store
4 | * Immediately redirects user to login page
5 | * Responsible for cleaning up authentication data, like tokens stored in local storage
6 | * @return {Promise}
7 | */
8 | export default () => {
9 | localStorage.removeItem("token");
10 | localStorage.removeItem("permissions");
11 | localStorage.removeItem("userInfo");
12 | return Promise.resolve();
13 | };
14 |
--------------------------------------------------------------------------------
/pharmd-app/src/twin.d.ts:
--------------------------------------------------------------------------------
1 | import 'twin.macro'
2 | import styledImport, {css as cssImport, CSSProp} from 'styled-components'
3 |
4 | declare module 'twin.macro' {
5 | // The styled and css imports
6 | const styled: typeof styledImport
7 | const css: typeof cssImport
8 | }
9 |
10 | declare module 'react' {
11 | // The css prop
12 | interface HTMLAttributes extends DOMAttributes {
13 | css?: CSSProp
14 | }
15 | // The inline svg css prop
16 | interface SVGProps extends SVGProps {
17 | css?: CSSProp
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/index.js:
--------------------------------------------------------------------------------
1 | import login from "./login";
2 | import logout from "./logout";
3 | import authError from "./authError";
4 | import isAuth from "./isAuth";
5 | import getPermissions from "./getPermissions";
6 |
7 | /**
8 | * Main source of authentication logic for the app
9 | * Object keys are hooks to execute authentication code
10 | * Provided to the components authProvider prop to connect to these hooks
11 | */
12 | export default {
13 | login,
14 | logout,
15 | checkError: authError,
16 | checkAuth: isAuth,
17 | getPermissions
18 | };
19 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/TemporaryField.js:
--------------------------------------------------------------------------------
1 | // This field is strictly for demonstration purposes only
2 |
3 | import React from "react";
4 | import tw, { css, styled } from "twin.macro";
5 |
6 | const Field = styled.span`
7 | ${tw`fontStyle-6 text-gray-700 font-medium`}
8 |
9 | ${props =>
10 | props.primary &&
11 | css`
12 | ${tw`fontStyle-6 text-secondary font-semibold`}
13 | `}
14 | font-size: 1.4em;
15 | `;
16 |
17 | const TempField = ({ source, isEmphasis }) => {
18 | return {source} ;
19 | };
20 |
21 | export default TempField;
22 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/upload/UploadResource.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import tw, { styled } from "twin.macro";
4 |
5 | import AppBar from "../../components/Nav/AppBar";
6 | import SpreadsheetUploader from "./components/SpreadsheetUploader";
7 |
8 | const MainContent = styled.div`
9 | ${tw`p-12 pt-2 `}
10 | flex-grow: 1;
11 | `;
12 |
13 | const UploadScreen = props => {
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
24 | export default UploadScreen;
25 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/expansionPanel.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ExpansionPanel from "../components/Basic/ExpansionPanel";
3 |
4 | export const Default = () => (
5 | TEST
}
7 | DetailChild={
8 |
9 | {" "}
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
11 | sit amet blandit leo lobortis eget. Lorem ipsum dolor sit amet, consectetur adipiscing
12 | elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.
13 |
14 | }
15 | />
16 | );
17 |
--------------------------------------------------------------------------------
/pharmd-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Checkbox Controls/CheckboxButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a single checkbox button.
4 | * TODO:
5 | * Date: 04-23-2020
6 | */
7 |
8 | // -------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React from "react";
12 |
13 | // Component Imports
14 | import Checkbox from "@material-ui/core/Checkbox";
15 |
16 | // -------------------------- COMPONENT --------------------------
17 | const CheckboxButton = props => {
18 | return ;
19 | };
20 |
21 | export default CheckboxButton;
22 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfileForm.js:
--------------------------------------------------------------------------------
1 | import {BooleanInput, required, SimpleForm, TextInput} from "react-admin";
2 | import React from "react";
3 |
4 | export default () =>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/CreateHooks.js:
--------------------------------------------------------------------------------
1 | // React Admin hooks that will makes calls to the API to CREATE content
2 |
3 | /**
4 | * Returns object with hooks for creating resources
5 | * @param {Function} httpClient The function to make an HTTP request
6 | * @param {string} BACKEND_URL the base URL to make a request to
7 | * @return {{create: (function(*, *): *)}}
8 | */
9 | export default (httpClient, BACKEND_URL) => ({
10 | create: (resource, params) =>
11 | httpClient(`${BACKEND_URL}/${resource}`, {
12 | method: "POST",
13 | body: JSON.stringify(params.data)
14 | }).then(({ json }) => ({
15 | data: { ...params.data, id: json.id }
16 | }))
17 | });
18 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/radioGroup.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import FormControlLabel from "@material-ui/core/FormControlLabel";
4 | import RadioButtonsGroup from "../components/Basic/Radio Controls/RadioButtonGroup";
5 |
6 | export const Default = () => (
7 |
8 | console.log("NEW VALUE", newVal)}
10 | label="Origin"
11 | >
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfilePage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import tw, { styled } from "twin.macro";
4 | import PropTypes from "prop-types";
5 |
6 | import AppBar from "../../components/Nav/AppBar";
7 |
8 | const MainContent = styled.div`
9 | ${tw`p-12 pt-2`}
10 | flex-grow: 1;
11 | flex-direction: row;
12 | `;
13 |
14 | const ProfilePage = props => {
15 | return (
16 | <>
17 |
18 |
19 | {props.children}
20 |
21 | >
22 | );
23 | };
24 |
25 | ProfilePage.propTypes = {
26 | pageName: PropTypes.string.isRequired
27 | };
28 |
29 | export default ProfilePage;
30 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/httpClient.js:
--------------------------------------------------------------------------------
1 | // Add authorization token to each request
2 | import { fetchUtils } from "react-admin";
3 |
4 | /**
5 | * Wrapper for React Admin HTTP JSON fetcher, adding headers and token to each request
6 | * @param {string} url the url to make a request to
7 | * @param {Object?} options Optionally contains headers for this request
8 | * @return {Promise}
9 | */
10 | export default (url, options = {}) => {
11 | if (!options.headers) {
12 | options.headers = new Headers({ Accept: "application/json" });
13 | }
14 | const token = localStorage.getItem("token");
15 | options.headers.set("Authorization", `Bearer ${token}`);
16 | return fetchUtils.fetchJson(url, options);
17 | };
18 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/scoredListField.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { text } from "@storybook/addon-knobs";
3 | import List from "@material-ui/core/List";
4 | import ScoredListItem from "../components/Fields/ScoredListField/ScoredListItem";
5 |
6 | const groupId = "GROUP-ID1";
7 |
8 | const primaryText = "Primary";
9 | const defaultPrimaryText = "Primary Text";
10 |
11 | export const Loading = () => (
12 |
13 |
14 |
15 | );
16 |
17 | export const Primary = () => (
18 |
19 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/studentDetails/StudentDetailsSide.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { styled } from "twin.macro";
4 | import QuickProfileField from "../../components/Fields/QuickProfileField";
5 | import QuickInfoField from "../../components/Fields/QuickInfoField";
6 | import NoteListField from "../../components/Fields/NoteListField";
7 |
8 | const Details = styled.div`
9 | padding: 8px 24px 24px;
10 | `;
11 |
12 | const StudentDetailsSide = ({ source }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default StudentDetailsSide;
23 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/test.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/diagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/CohortField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import InfoIcon from "../Basic/IconInfo";
4 | import ErrorIcon from "../../assets/icons/errorFilled.svg";
5 |
6 | const Field = styled.span`
7 | ${tw`fontStyle-6 text-gray-700 font-medium inline-flex`}
8 | `;
9 |
10 | const Offset = styled.span`
11 | ${tw`ml-3`}
12 | `;
13 |
14 | const CohortField = ({ record = {}, source }) => {
15 | const cohort = record[source];
16 | return (
17 |
18 | {cohort.current}
19 | {cohort.moved && (
20 |
21 |
22 |
23 | )}
24 |
25 | );
26 | };
27 | export default CohortField;
28 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/authError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Each time the API returns an error, react-admin calls the authProvider.checkError() method.
3 | * It’s up to this function to decide which HTTP status codes should let the user continue (by returning a resolved promise)
4 | * or log them out (by returning a rejected promise).
5 | * If the API requires authentication, and the user credentials are missing in the request or invalid,
6 | * the API usually answers with an HTTP error code 401 or 403.
7 | * @param {number} status
8 | * @return {Promise|Promise}
9 | */
10 | export default ({ status }) => {
11 | if (status === 401 || status === 403) {
12 | localStorage.removeItem("token");
13 | return Promise.reject();
14 | }
15 | return Promise.resolve();
16 | };
17 |
--------------------------------------------------------------------------------
/pharmd-app/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { addDecorator, configure } from "@storybook/react";
2 |
3 | import { withThemesProvider } from "storybook-addon-styled-component-theme";
4 | import ThemeProvider from "./theme-provider";
5 |
6 | import createLigthTheme from "../src/themes/light-theme";
7 | import createDarkTheme from "../src/themes/dark-theme";
8 |
9 | import "../src/styles/App.css";
10 |
11 | const themes = [createLigthTheme(), createDarkTheme()];
12 | addDecorator(withThemesProvider(themes, ThemeProvider));
13 |
14 | configure(require.context("../src/stories", true, /\.stories\.js$/), module);
15 | // stories: ['../src/**/*.stories.js'],
16 | // addons: [
17 | // '@storybook/preset-create-react-app',
18 | // '@storybook/addon-actions',
19 | // '@storybook/addon-links',
20 | // ],
21 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/index.js:
--------------------------------------------------------------------------------
1 | // Learn more here: https://marmelab.com/react-admin/DataProviders.html
2 |
3 | import { BACKEND_URL } from "../../config/backendRoutes";
4 | import DeleteHooks from "./DeleteHooks";
5 | import GetHooks from "./GetHooks";
6 | import httpClient from "./httpClient";
7 | import EditHooks from "./EditHooks";
8 | import UpdateHooks from "./UpdateHooks";
9 | import CreateHooks from "./CreateHooks";
10 |
11 | // import real BACKEND_URL to use pharmD backend data
12 | // const BACKEND_URL = FAKE_API;
13 |
14 | export default {
15 | ...GetHooks(httpClient, BACKEND_URL),
16 | ...CreateHooks(httpClient, BACKEND_URL),
17 | ...EditHooks(httpClient, BACKEND_URL),
18 | ...UpdateHooks(httpClient, BACKEND_URL),
19 | ...DeleteHooks(httpClient, BACKEND_URL)
20 | };
21 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/QuickInfo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 |
4 | const InfoDisplay = styled.div`
5 | width: calc(100% / 3);
6 | display: inline-block;
7 | text-align: center;
8 | margin-left: auto;
9 | margin-right: auto;
10 | `;
11 |
12 | const Heading = styled.h2`
13 | ${tw` fontStyle-6 text-sm`}
14 | font-weight: bold;
15 | text-align: center;
16 | `;
17 |
18 | const Detail = styled.h3`
19 | color: #2b2b90;
20 | text-align: center;
21 | font-size: 1.5em;
22 | margin-bottom: 0;
23 | `;
24 |
25 | const QuickInfo = ({ info, label }) => {
26 | return (
27 |
28 | {info}
29 | {label}
30 |
31 | );
32 | };
33 |
34 | export default QuickInfo;
35 |
--------------------------------------------------------------------------------
/pharmd-app/src/config/backendRoutes.js:
--------------------------------------------------------------------------------
1 | export const BACKEND_URL = process.env.API_URL;
2 | export const FAKE_API = "https://student-db-remote.herokuapp.com";
3 | export const { AUTH_URL } = process.env;
4 | export const FAKE_AUTH = "https://student-db-remote.herokuapp.com/login";
5 |
6 | export const BACKEND_STUDENTS = `${BACKEND_URL}students/`;
7 | export const BACKEND_STUDENTASSESSMENTS = `${BACKEND_URL}students-assessments/`;
8 | export const BACKEND_SESSIONS = `${BACKEND_URL}sessions/`;
9 | export const BACKEND_ASSESSMENTS = `${BACKEND_URL}assessments/`;
10 | export const BACKEND_COURSES = `${BACKEND_URL}courses/`;
11 | export const BACKEND_NOTES = `${BACKEND_URL}notes/`;
12 | export const BACKEND_STUDENTCOURSES = `${BACKEND_URL}student-courses/`;
13 | export const BACKEND_USERS = `${BACKEND_URL}users/`;
14 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Table/Table.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | // Style Imports
6 | import tw, { styled } from "twin.macro";
7 | // Component Imports
8 | import { Datagrid as DatagridRA } from "react-admin";
9 |
10 | // -------------------------- STYLE --------------------------
11 | const Datagrid = styled(DatagridRA)`
12 | .headerCell {
13 | ${tw`h-18 fontStyle-5 tracking-wide text-gray-600`}
14 | }
15 |
16 | .rowCell {
17 | ${tw`h-16`}
18 | }
19 | `;
20 |
21 | // -------------------------- COMPONENT --------------------------
22 | const Table = props => {
23 | return ;
24 | };
25 |
26 | export default Table;
27 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Nav/NavIndicator.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | // Style Imports
6 | import tw, { styled } from "twin.macro";
7 |
8 | // -------------------------- STYLE --------------------------
9 | const Indicator = styled.div`
10 | ${tw`w-1 h-24 absolute rounded-l`}
11 |
12 | background: ${props => props.theme.palette.primary.main};
13 | left: 92px;
14 | right: 0;
15 | top: ${props => props.top}px;
16 | bottom: 0;
17 | transition: top 0.3s ease-in-out;
18 | `;
19 |
20 | // -------------------------- COMPONENT --------------------------
21 | const NavIndicator = ({ index = 0 }) => {
22 | const h = 96;
23 | return ;
24 | };
25 |
26 | export default NavIndicator;
27 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/studentSearchInput.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { text, withKnobs } from "@storybook/addon-knobs";
3 |
4 | import { Filter } from "react-admin";
5 | import StudentSearchInput from "../components/Inputs/StudentSearchInput";
6 |
7 | export default {
8 | title: "Search Input",
9 | component: StudentSearchInput,
10 | decorators: [withKnobs]
11 | };
12 |
13 | const groupId = "GROUP-ID3";
14 |
15 | const label = "Helper Text";
16 | const defaultLabel = "Search";
17 |
18 | export const Default = () => (
19 |
20 | `^${inputValue}`}
24 | onChange={() => Console.log()}
25 | alwaysOn
26 | />
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/EditHooks.js:
--------------------------------------------------------------------------------
1 | // React Admin hooks that will makes calls to the API to EDIT content
2 |
3 | // NOTE: this may be added for compatibility with fake API--edit hook does not exist in React Admin
4 |
5 | /**
6 | * Returns object with hooks for editing resources
7 | * @param {Function} httpClient The function to make an HTTP request
8 | * @param {string} BACKEND_URL the base URL to make a request to
9 | * @return {{edit: (function(*=, *=): *)}}
10 | */
11 | export default (httpClient, BACKEND_URL) => ({
12 | edit: (resource, params) => {
13 | return httpClient(`${BACKEND_URL}/${resource}`, {
14 | method: "PUT",
15 | body: JSON.stringify(params.data)
16 | }).then(({ json }) => {
17 | return {
18 | data: { ...params.data, id: json.id }
19 | };
20 | });
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/filter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/ChipField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import { Chip as MuiChip } from "@material-ui/core";
4 | import { getFieldBackgroundColor, getFieldColor } from "../../themes/field-colors";
5 |
6 | const Pill = styled(MuiChip)`
7 | ${tw`rounded-lg capitalize w-28 fontStyle-4 font-bold tracking-wider`}
8 | font-size: 1.1em;
9 | color: ${props => props.color};
10 | background-color: ${props => props.backgroundColor};
11 | `;
12 |
13 | const ChipField = props => {
14 | const readableStatus = props.record[props.source]
15 | .replace("_", " ")
16 | .split(" ")
17 | .map(s => s.charAt(0) + s.slice(1).toLowerCase())
18 | .join(" ");
19 |
20 | return (
21 |
26 | );
27 | };
28 |
29 | export default ChipField;
30 |
--------------------------------------------------------------------------------
/pharmd-app/src/plugins/type-system/index.js:
--------------------------------------------------------------------------------
1 | function getTypeScale() {
2 | const types = {};
3 | // TODO: Currently we have 12 font styles, yet we might add more in the future
4 | // Create a function that retrieves the unique number of Font Style css variables
5 |
6 | for (let i = 1; i <= 12; i++) {
7 | types[`.fontStyle-${i}`] = {
8 | fontSize: `var(--font-style-${i}-font-size)`,
9 | fontFamily: `var(--font-style-${i}-font-family)`,
10 | fontStyle: `var(--font-style-${i}-font-style)`,
11 | letterSpacing: `var(--font-style-${i}-letter-spacing)`,
12 | lineHeight: `var(--font-style-${i}-line-height)`
13 | };
14 | }
15 |
16 | return types;
17 | }
18 |
19 | const customTypeStyles = {};
20 |
21 | module.exports = function(variants) {
22 | return function({ addUtilities }) {
23 | addUtilities(
24 | {
25 | ...getTypeScale(),
26 | ...customTypeStyles
27 | },
28 | variants
29 | );
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import ProfileEdit from "./ProfileEdit";
4 | import ProfileCreate from "./ProfileCreate";
5 | import ProfileShow from "./ProfileShow";
6 | import Avatar from "../../components/Basic/Avatar";
7 | import ProfileManage from "./ProfileManage";
8 |
9 | const AvatarContainer = styled.div`
10 | ${tw`flex justify-center items-center w-24 h-24`}
11 | `;
12 |
13 | const AvatarIcon = () => {
14 | const info = JSON.parse(localStorage.getItem("userInfo"));
15 | let props = {};
16 | if (info) {
17 | props = {
18 | firstName: info.firstName,
19 | lastName: info.lastName
20 | };
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default {
31 | list: ProfileManage,
32 | show: ProfileShow,
33 | create: ProfileCreate,
34 | edit: ProfileEdit,
35 | icon: AvatarIcon
36 | };
37 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/ScoredListField/ScoredListField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useGetOne } from "react-admin";
3 | import List from "@material-ui/core/List";
4 | import ScoredListItem from "./ScoredListItem";
5 |
6 | const ScoredListField = ({ record = {}, source }) => {
7 | const courses = record[source];
8 | return (
9 |
10 | {courses.map((course, index) => {
11 | // TODO: Need to fix render issue here
12 | const { data, loading, error } = useGetOne("courses", course.course_id);
13 |
14 | return (
15 |
24 | );
25 | })}
26 |
27 | );
28 | };
29 |
30 | export default ScoredListField;
31 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/FilterChip.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a multi select drop down / text input to filter Student data based on GPA.
4 | * This component also allows the user to type in the option they are looking for to filter the options.
5 | *
6 | * Date: 03-18-2021
7 | */
8 |
9 | // -------------------------- IMPORTS --------------------------
10 | // Function Imports
11 | import React from "react";
12 |
13 | // Component Imports
14 | import Chip from "@material-ui/core/Chip";
15 | import ClearIcon from '@material-ui/icons/Clear';
16 |
17 | // Style Imports
18 | import tw from "twin.macro";
19 |
20 | // -------------------------- COMPONENT --------------------------
21 | const FilterChip = ({label, ...props}) => {
22 | return (
23 | }
27 | label={label}
28 | />
29 | );
30 | }
31 |
32 | export default FilterChip;
33 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/tooltip.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { select, text, withKnobs } from "@storybook/addon-knobs";
3 | import Tooltip from "../components/Basic/Tooltip";
4 |
5 | export default {
6 | title: "Tooltip",
7 | component: Tooltip,
8 | decorators: [withKnobs]
9 | };
10 |
11 | const label = "Placement";
12 | const options = {
13 | Right: "right",
14 | Left: "left",
15 | Bottom: "bottom",
16 | Top: "top"
17 | };
18 | const defaultValue = "right";
19 | const groupId = "GROUP-ID1";
20 |
21 | const labelText = "Title(Label)";
22 | const defaultValueText = "Title";
23 |
24 | export const Default = () => (
25 |
26 |
31 | Child
32 |
33 |
34 | );
35 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/NoteIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SvgIcon from "@material-ui/core/SvgIcon";
3 | import { css, styled } from "twin.macro";
4 |
5 | const SVG = styled(SvgIcon)`
6 | vertical-align: middle;
7 | ${props =>
8 | props.color === "grey" &&
9 | css`
10 | color: ${props => props.theme.palette.tertiary.main};
11 | `}
12 |
13 | ${props =>
14 | props.color === "black" &&
15 | css`
16 | color: black;
17 | `}
18 |
19 | ${props =>
20 | props.primary &&
21 | css`
22 | margin-left: auto;
23 | `}
24 | `;
25 |
26 | const NoteIcon = ({ src, color = "inherit", size, htmlColor, isPrimary }) => {
27 | const inputProps = {
28 | component: src,
29 | htmlColor: htmlColor || undefined,
30 | color: htmlColor ? undefined : color
31 | };
32 |
33 | // spread declaration to add props
34 | return ;
35 | };
36 |
37 | export default NoteIcon;
38 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/QuickChipField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import { Chip as MuiChip } from "@material-ui/core";
4 | import { getFieldBackgroundColor, getFieldColor } from "../../themes/field-colors";
5 |
6 | const Pill = styled(MuiChip)`
7 | ${tw`rounded-lg capitalize w-24 fontStyle-4 font-bold tracking-wider`}
8 | font-size: ${props => props.label === "Graduated" ? `0.75em` : `0.8em`};
9 | color: ${props => props.color};
10 | background-color: ${props => props.backgroundColor};
11 | `;
12 |
13 | const QuickChipField = props => {
14 | const readableStatus = props.record[props.source]
15 | .replace("_", " ")
16 | .split(" ")
17 | .map(s => s.charAt(0) + s.slice(1).toLowerCase())
18 | .join(" ");
19 |
20 | return (
21 |
26 | );
27 | };
28 |
29 | export default QuickChipField;
30 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/navItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter as Router } from "react-router-dom";
3 | import TestIcon from "../assets/icons/student.svg";
4 | import NavItemLink from "../components/Nav/NavItemLink";
5 |
6 | export const Default = () => {
7 | const ref = React.createRef();
8 |
9 | return (
10 |
11 | {}}
17 | isOpen={false}
18 | ref={ref}
19 | />
20 |
21 | );
22 | };
23 |
24 | export const Active = () => {
25 | const ref = React.createRef();
26 |
27 | return (
28 |
29 | {}}
35 | isOpen={false}
36 | isActive
37 | ref={ref}
38 | />
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfileEdit.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BooleanInput, Edit, required, SimpleForm, TextInput } from "react-admin";
3 | import ProfilePage from "./ProfilePage";
4 |
5 | const EditForm = props => (
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 | );
22 |
23 | const ProfileEdit = props => {
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default ProfileEdit;
32 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/DeleteHooks.js:
--------------------------------------------------------------------------------
1 | // React Admin hooks that will makes calls to the API to DELETE content
2 |
3 | /**
4 | * Returns object with hooks for deleting resources
5 | * @param {Function} httpClient The function to make an HTTP request
6 | * @param {string} BACKEND_URL the base URL to make a request to
7 | * @return {{deleteMany: (function(*, *): Promise<{data: *}>), delete: (function(*, *): *)}}
8 | */
9 | export default (httpClient, BACKEND_URL) => ({
10 | delete: (resource, params) =>
11 | httpClient(`${BACKEND_URL}/${resource}/${params.id}`, {
12 | method: "DELETE"
13 | }).then(({ json }) => ({ data: json })),
14 |
15 | // json-server doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead
16 | deleteMany: (resource, params) =>
17 | Promise.all(
18 | params.ids.map(id =>
19 | httpClient(`${BACKEND_URL}/${resource}/${id}`, {
20 | method: "DELETE"
21 | })
22 | )
23 | ).then(responses => ({ data: responses.map(({ json }) => json.id) }))
24 | });
25 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfileCreate.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BooleanInput, Create, required, SimpleForm, TextInput } from "react-admin";
3 | import ProfilePage from "./ProfilePage";
4 |
5 | const CreateForm = props => (
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 | );
22 |
23 | const ProfileCreate = props => {
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default ProfileCreate;
32 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Avatar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import AvatarMaterial from "@material-ui/core/Avatar";
3 | import tw, { styled } from "twin.macro";
4 | import Icon from "./Icon";
5 | import Person from "../../assets/icons/person.svg";
6 |
7 | const AvatarStyled = styled(AvatarMaterial)`
8 | ${tw`w-12 h-12`}
9 | &.MuiAvatar-colorDefault {
10 | ${tw``}
11 | }
12 | `;
13 |
14 | const PictureStyled = styled(AvatarMaterial)`
15 | display: block;
16 | height: 120px;
17 | width: 120px;
18 | margin-left: auto;
19 | margin-right: auto;
20 | `;
21 |
22 | const Avatar = ({ firstName, lastName, imgUrl }) => {
23 | if (imgUrl) {
24 | const fullName = `${firstName} ${lastName}`;
25 | return ;
26 | }
27 | if (firstName) {
28 | const initials = `${firstName.charAt(0)}${lastName ? lastName.charAt(0) : ""}`;
29 | return {initials} ;
30 | }
31 | return (
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Avatar;
39 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/avatar.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { text, withKnobs } from "@storybook/addon-knobs";
3 | import { withDesign } from "storybook-addon-designs";
4 | import Avatar from "../components/Basic/Avatar";
5 | import imgProfile from "../assets/images/mountains.jpg";
6 |
7 | export default {
8 | title: "Avatar",
9 | component: Avatar,
10 | decorators: [withKnobs, withDesign]
11 | };
12 |
13 | const groupId = "GROUP-ID2";
14 |
15 | const firstName = "First Name";
16 | const defaultFirstName = "";
17 |
18 | const lastName = "Last Name";
19 | const defaultLastName = "";
20 |
21 | export const Default = () => (
22 |
26 | );
27 |
28 | Default.story = {
29 | parameters: {
30 | design: {
31 | type: "figma",
32 | url:
33 | "https://www.figma.com/file/QBWcVYJkF6K8o7WErRPsxd/Student-List-Mockups?node-id=15%3A39"
34 | }
35 | }
36 | };
37 |
38 | export const Image = () => ;
39 |
--------------------------------------------------------------------------------
/pharmd-app/src/themes/dark-theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from "@material-ui/core/styles";
2 | import green from "@material-ui/core/colors/green";
3 | import purple from "@material-ui/core/colors/purple";
4 |
5 | const primaryGreen = green[500];
6 | const accentGreen = green.A200;
7 | const darkGreen = green[900];
8 | const primaryPurple = purple[500];
9 | const accentPurple = purple.A200;
10 | const darkPurple = purple[900];
11 |
12 | function createDarkTheme() {
13 | const overridings = {
14 | name: "Dark Theme",
15 | palette: {
16 | awesomeColors: {
17 | primary: primaryGreen,
18 | secondary: accentPurple
19 | },
20 | primary: {
21 | light: accentPurple,
22 | main: primaryPurple,
23 | dark: darkPurple,
24 | contrastText: "#fff"
25 | },
26 | type: "dark",
27 | secondary: {
28 | light: accentGreen,
29 | main: primaryGreen,
30 | dark: darkGreen,
31 | contrastText: "#fff"
32 | }
33 | }
34 | };
35 |
36 | return createMuiTheme(overridings);
37 | }
38 |
39 | export default createDarkTheme;
40 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/CourseField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { css, styled } from "twin.macro";
3 |
4 | const Info = styled.div`
5 | ${tw`fontStyle-6 text-black font-medium inline-flex`}
6 | width: 100%;
7 | margin-top: 10px;
8 | padding-bottom: 10px;
9 | border-bottom: 1px solid #dcdcdc;
10 | font-size: 0.89em;
11 | ${props =>
12 | props.primary &&
13 | css`
14 | border-bottom: 0px;
15 | `}
16 | `;
17 |
18 | const Name = styled.div`
19 | position: relative;
20 | `;
21 |
22 | const Grade = styled.div`
23 | font-weight: bold;
24 | position: relative;
25 | margin-left: auto;
26 | `;
27 |
28 | const Circle = styled.span`
29 | margin-top: auto;
30 | margin-bottom: auto;
31 | margin-right: 15px;
32 | height: 15px;
33 | width: 15px;
34 | background-color: red;
35 | border-radius: 50%;
36 | `;
37 |
38 | const CourseField = ({ name, grade, isLine }) => {
39 | return (
40 |
41 |
42 | {name}
43 | {`${grade}%`}
44 |
45 | );
46 | };
47 |
48 | export default CourseField;
49 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/UpdateHooks.js:
--------------------------------------------------------------------------------
1 | // React Admin hooks that will makes calls to the API to UPDATE content
2 |
3 | /**
4 | * Returns object with hooks for updating resources
5 | * @param {Function} httpClient The function to make an HTTP request
6 | * @param {string} BACKEND_URL the base URL to make a request to
7 | * @return {{updateMany: (function(*, *): Promise<{data: *}>), update: (function(*, *): *)}}
8 | */
9 | export default (httpClient, BACKEND_URL) => ({
10 | update: (resource, params) =>
11 | httpClient(`${BACKEND_URL}/${resource}/${params.id}`, {
12 | method: "PUT",
13 | body: JSON.stringify(params.data)
14 | }).then(({ json }) => ({ data: json })),
15 |
16 | // json-server doesn't handle filters on UPDATE route, so we fallback to calling UPDATE n times instead
17 | updateMany: (resource, params) =>
18 | Promise.all(
19 | params.ids.map(id =>
20 | httpClient(`${BACKEND_URL}/${resource}/${id}`, {
21 | method: "PUT",
22 | body: JSON.stringify(params.data)
23 | })
24 | )
25 | ).then(responses => ({ data: responses.map(({ json }) => json.id) }))
26 | });
27 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/studentDetails/StudentExamList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ReferenceManyField } from "react-admin";
3 | import TextField from "../../components/Fields/TextField";
4 | import EmphasisField from "../../components/Fields/EmphasisField";
5 | import GradeField from "../../components/Fields/GradeField";
6 | import Table from "../../components/Table/Table";
7 | import { STUDENT_EXAM } from "../../constants/apiObjects";
8 |
9 | const StudentExamList = ({ resource, basePath, record }) => {
10 | return (
11 |
19 |
25 |
26 | );
27 | };
28 | export default StudentExamList;
29 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Utility.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | /**
4 | * Creates an object from
5 | * @param {Array} inputArray
6 | * @param {Array} keySourceArray
7 | * @return {Object}
8 | */
9 | export function arrayToObject(inputArray, keySourceArray) {
10 | const obj = {};
11 | inputArray.forEach((value, index) => (obj[keySourceArray[index]] = value));
12 | return obj;
13 | }
14 |
15 | export const doAllRequests = requests => axios.all(requests);
16 |
17 |
18 | export function formatDecimal(preformatted) {
19 | const preformattedString = preformatted.toString();
20 | const decimalRegex = /^(?\d*)(?\.)?(?\d*)?/;
21 | const numPlaces = 2;
22 |
23 | const matches = preformattedString.match(decimalRegex);
24 | const wholeNumber = matches.groups.wholeNumber;
25 | const point = matches.groups.point || '.';
26 | let decimal = matches.groups.decimal || '00';
27 |
28 | while (decimal.length < numPlaces) {
29 | decimal = decimal + '0';
30 | }
31 |
32 | if (decimal.length > numPlaces) {
33 | decimal = decimal.substring(0, numPlaces);
34 | }
35 |
36 | return wholeNumber + point + decimal;
37 | }
38 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Tooltip.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ToolTipMaterial from "@material-ui/core/Tooltip";
3 | import tw, { styled } from "twin.macro";
4 | import Zoom from "@material-ui/core/Zoom";
5 |
6 | /*
7 | NOTE: Uses Portals to style component.
8 | The Portal provides a first-class way to render children
9 | into a DOM node that exists outside the DOM hierarchy of
10 | the parent component
11 | */
12 |
13 | const TooltipStyled = styled(({ className, ...props }) => (
14 |
15 | ))`
16 | ${tw`py-2 px-4 rounded-lg fontStyle-2 text-white`}
17 | background-color: var(--color-bg-tooltip);
18 | font-weight: bold;
19 |
20 | .MuiTooltip-arrow {
21 | color: var(--color-bg-tooltip);
22 | }
23 | `;
24 |
25 | const Tooltip = ({ title, placement = "right", ...props }) => {
26 | return (
27 |
35 | {props.children}
36 |
37 | );
38 | };
39 |
40 | export default Tooltip;
41 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Icon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SvgIcon from "@material-ui/core/SvgIcon";
3 | import tw, { css, styled } from "twin.macro";
4 |
5 | const SVG = styled(SvgIcon)`
6 |
7 | transition: color 0.5s ease;
8 |
9 | &.MuiSvgIcon-colorDisabled {
10 | ${tw`text-gray-400`}
11 | }
12 |
13 | &.MuiSvgIcon-colorAction {
14 | ${tw`text-gray-600`}
15 | }
16 |
17 | ${props =>
18 | props.color === "tertiary" &&
19 | css`
20 | color: ${props => props.theme.palette.tertiary.main};
21 | `}
22 |
23 | ${props =>
24 | props.color === "black" &&
25 | css`
26 | color: #333;
27 | `}
28 |
29 | ${props =>
30 | props.color === "white" &&
31 | css`
32 | color: #fff;
33 | `}
34 | `;
35 |
36 | const Icon = ({ src, color = "inherit", size = "default", accessibleTitle, htmlColor }) => {
37 | const inputProps = {
38 | component: src,
39 | titleAccess: accessibleTitle || undefined,
40 | htmlColor: htmlColor || undefined,
41 | color: htmlColor ? undefined : color
42 | };
43 |
44 | return ;
45 | };
46 |
47 | export default Icon;
48 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/GradeField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import { FunctionField } from "react-admin";
4 | import TextField from "./TextField";
5 | import { getFieldColor } from "../../themes/field-colors";
6 | import { STUDENT_COURSE } from "../../constants/apiObjects";
7 | import { formatDecimal } from '../../services/Utility'
8 |
9 | const StyledTextField = styled(TextField)`
10 | ${tw`fontStyle-6 text-gray-700 font-medium`}
11 |
12 | color: ${props => props.color};
13 | font-size: 1.4em;
14 | `;
15 |
16 | const StyledFunctionField = styled(FunctionField)`
17 | ${tw`fontStyle-6 text-gray-700 font-medium`}
18 |
19 | color: ${props => props.color};
20 | font-size: 1.4em;
21 | `;
22 |
23 | const GradeField = props => {
24 | return props.source === STUDENT_COURSE.LETTER_GRADE ?
25 | (
26 |
27 | ) : (
28 | `${formatDecimal(record[props.source])}`}
30 | color={getFieldColor(props)}
31 | {...props}
32 | />
33 | );
34 | };
35 |
36 | export default GradeField;
37 |
--------------------------------------------------------------------------------
/pharmd-app/.storybook/theme-provider.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import CssBaseline from "@material-ui/core/CssBaseline";
4 | import { MuiThemeProvider } from "@material-ui/core/styles";
5 | import { StylesProvider } from "@material-ui/styles";
6 | import { ThemeProvider as StyledThemeProvider } from "styled-components";
7 |
8 | const ThemeProvider = ({ theme, children }) => {
9 | const nextTheme = Object.assign({}, theme);
10 |
11 | var d = document.documentElement;
12 | if (nextTheme.name === "Dark Theme") {
13 | d.classList.add("theme-dark");
14 | } else if (nextTheme.name === "Light Theme") {
15 | d.classList.remove("theme-dark");
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 |
23 | {children}
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | ThemeProvider.propTypes = {
31 | children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
32 | theme: PropTypes.object.isRequired
33 | };
34 |
35 | export default ThemeProvider;
36 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/help.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/studentDetails/StudentCourseList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ReferenceManyField } from "react-admin";
3 | import TextField from "../../components/Fields/TextField";
4 | import EmphasisField from "../../components/Fields/EmphasisField";
5 | import GradeField from "../../components/Fields/GradeField";
6 | import Table from "../../components/Table/Table";
7 | import { STUDENT_COURSE } from "../../constants/apiObjects";
8 |
9 | const StudentCourseList = ({ resource, basePath, record }) => {
10 | return (
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 | export default StudentCourseList;
30 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentQuickView.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Show, SimpleShowLayout as SimpleShowLayoutRA, useEditController } from "react-admin";
4 |
5 | import { styled } from "twin.macro";
6 | import QuickProfileField from "../../components/Fields/QuickProfileField";
7 | import QuickInfoField from "../../components/Fields/QuickInfoField";
8 |
9 | import NoteListField from "../../components/Fields/NoteListField";
10 | /*
11 | TODO: Material Skeleton for loading
12 | */
13 |
14 | const SimpleShowLayout = styled(SimpleShowLayoutRA)`
15 | &.MuiCard-root {
16 | box-shadow: none;
17 | }
18 | `;
19 |
20 | const StudentQuickView = props => {
21 | const controllerProps = useEditController(props);
22 | if (!controllerProps.record) {
23 | return null;
24 | }
25 | return (
26 |
27 |
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default StudentQuickView;
41 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Table/TableRow.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React, { cloneElement } from "react";
5 | // Style Imports
6 | import tw, { styled } from "twin.macro";
7 | // Component Imports
8 | import TableCell from "@material-ui/core/TableCell";
9 | import MuiTableRow from "@material-ui/core/TableRow";
10 |
11 | // -------------------------- STYLE --------------------------
12 | const TableRowStyled = styled(MuiTableRow)`
13 | &.MuiTableRow-root {
14 | ${tw`h-16`}
15 | }
16 |
17 | & .MuiTableCell-head {
18 | ${tw`h-18 fontStyle-5 tracking-wide text-gray-600`}
19 | }
20 | `;
21 |
22 | // -------------------------- COMPONENT --------------------------
23 | const TableRow = ({ record, resource, id, children, selected, basePath }) => {
24 | return (
25 |
26 | {React.Children.map(children, field => (
27 |
28 | {cloneElement(field, {
29 | record,
30 | basePath,
31 | resource
32 | })}
33 |
34 | ))}
35 |
36 | );
37 | };
38 |
39 | export default TableRow;
40 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/courses/CourseList.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | import { Datagrid as DatagridRA } from "react-admin";
6 | import tw, { styled } from "twin.macro";
7 | import { COURSE } from "../../constants/apiObjects";
8 | // Component Imports
9 | import EmphasisField from "../../components/Fields/EmphasisField";
10 | import TextField from "../../components/Fields/TextField";
11 |
12 | // Style Imports
13 |
14 | // -------------------------- STYLE --------------------------
15 |
16 | const Datagrid = styled(DatagridRA)`
17 | .headerCell {
18 | /* background-color: red; */
19 | ${tw`h-18 fontStyle-5 tracking-wide text-gray-600`}
20 | }
21 |
22 | .rowCell {
23 | ${tw`h-16`}
24 | }
25 | `;
26 |
27 | // -------------------------- COMPONENT --------------------------
28 |
29 | const CourseList = props => {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | );
37 | };
38 | export default CourseList;
39 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/courses/CourseResource.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | // Component Imports
6 | import { List as RaList } from "react-admin";
7 | import styled from "styled-components/macro";
8 | import tw from "tailwind.macro";
9 | import AppBar from "../../components/Nav/AppBar";
10 | import CourseList from "./CourseList";
11 | import { CourseFilter } from "./CourseFilter";
12 |
13 | // Style Imports
14 |
15 | // -------------------------- STYLE --------------------------
16 |
17 | const MainContent = styled.div`
18 | ${tw`p-12 pt-2 `}
19 | flex-grow: 1;
20 | `;
21 |
22 | const List = styled(RaList)`
23 | ${tw`p-6 bg-white rounded-xl`}
24 | margin-top: 50px;
25 |
26 | .MuiPaper-elevation1 {
27 | box-shadow: none;
28 | }
29 | `;
30 |
31 | // -------------------------- COMPONENT --------------------------
32 |
33 | const CourseResource = props => {
34 | return (
35 | <>
36 |
37 |
38 |
} {...props}>
39 |
40 |
41 |
42 | >
43 | );
44 | };
45 |
46 | export default CourseResource;
47 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Form/FileSelector.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Button from './Button';
3 | import PropTypes from "prop-types";
4 |
5 | class Selector extends React.Component {
6 | static propTypes = {
7 | onChoose: PropTypes.func
8 | };
9 |
10 | static defaultProps = {
11 | onChoose() {}
12 | };
13 |
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | fileName: 'Upload File'
18 | };
19 | this.onChooseFile = this.onChooseFile.bind(this);
20 | }
21 |
22 | onChooseFile(event) {
23 | const file = event.target.files[0];
24 | this.setState({
25 | fileName: file.name
26 | });
27 | this.props.onChoose(file)
28 | }
29 |
30 | render() {
31 | return
37 | {this.state.fileName}
38 |
44 | ;
45 | }
46 |
47 | }
48 |
49 | export default Selector;
50 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/icon.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { select, withKnobs } from "@storybook/addon-knobs";
3 | import Icon from "../components/Basic/Icon";
4 | import IconButtonI from "../components/Basic/IconButton";
5 | import TestIcon from "../assets/icons/student.svg";
6 |
7 | export default {
8 | title: "Icon",
9 | decorators: [withKnobs]
10 | };
11 |
12 | const label = "Color";
13 | const options = {
14 | Default: "inherit",
15 | Primary: "primary",
16 | Secondary: "secondary",
17 | Tertiary: "tertiary",
18 | Action: "action",
19 | Error: "error",
20 | Disable: "disabled",
21 | White: "white",
22 | Black: "black"
23 | };
24 | const defaultValue = "inherit";
25 | const groupId = "GROUP-ID1";
26 |
27 | const labelS = "Size";
28 | const optionsS = {
29 | Default: "inherit",
30 | Small: "small",
31 | Large: "large"
32 | };
33 | const defaultValueS = "inherit";
34 |
35 | export const Default = () => (
36 |
41 | );
42 |
43 | export const IconButton = () => (
44 |
45 |
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/QuickInfoField.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | import { Loading, useGetOne } from "react-admin";
6 | import { formatDecimal } from "../../services/Utility";
7 |
8 | // Component Imports
9 | import { styled } from "twin.macro";
10 | import QuickInfo from "../Basic/QuickInfo";
11 |
12 | // Style Imports
13 |
14 | // -------------------------- STYLE --------------------------
15 |
16 | const Info = styled.div`
17 | display: inline-block;
18 | margin-left: auto;
19 | margin-right: auto;
20 | position: relative;
21 | text-align: center;
22 | width: 100%;
23 | `;
24 |
25 | // -------------------------- COMPONENT --------------------------
26 |
27 | const QuickInfoField = ({ record = {}, source }) => {
28 | const id = record[source];
29 | const { data, loading, error } = useGetOne("students", id);
30 | if (loading) {
31 | return ;
32 | }
33 | if (error) {
34 | return Error, id:{id} {' '} is not found
;
35 | }
36 | return (
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default QuickInfoField;
45 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/NoteListField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Error, Loading, useQuery } from "react-admin";
3 | import tw, { styled } from "twin.macro";
4 | import NoteField from "./NoteField";
5 |
6 | const Label = styled.h1`
7 | ${tw`fontStyle-6 font-medium inline-flex`}
8 | font-size: 1.3em;
9 | color: black;
10 | font-weight: bold;
11 | `;
12 |
13 | const Notes = styled.div`
14 | margin-top: 12px;
15 | `;
16 |
17 | const NoteListField = ({ record = {}, source }) => {
18 | const { data, loading, error } = useQuery({
19 | type: "getManyReference",
20 | resource: "notes",
21 | payload: {
22 | target: "student",
23 | id: record[source],
24 | pagination: {
25 | page: 1,
26 | perPage: 10 // TODO: how many per page?
27 | },
28 | sort: {
29 | field: "", // TODO: Backend doesn't currently handle sorting for notes
30 | order: ""
31 | }
32 | }
33 | });
34 |
35 | if (loading) return ;
36 | if (error) return ;
37 | if (!data) return null;
38 |
39 | return (
40 |
41 | Recent Notes
42 | {data.map(note => {
43 | return ;
44 | })}
45 |
46 | );
47 | };
48 |
49 | export default NoteListField;
50 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/ExpansionPanel.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import ExpansionPanelMaterial from "@material-ui/core/ExpansionPanel";
4 | import ExpansionPanelSummaryMaterial from "@material-ui/core/ExpansionPanelSummary";
5 | import ExpansionPanelDetailsMaterial from "@material-ui/core/ExpansionPanelDetails";
6 |
7 | const ExpansionPanelC = styled(ExpansionPanelMaterial)`
8 | ::before {
9 | height: 0;
10 | }
11 | `;
12 |
13 | const ExpansionPanelSummary = styled(ExpansionPanelSummaryMaterial)`
14 | ${tw`p-0 m-0`}
15 |
16 | .MuiExpansionPanelSummary-content {
17 | ${tw`m-0`}
18 | }
19 | `;
20 |
21 | const ExpansionPanelDetails = styled(ExpansionPanelDetailsMaterial)`
22 | ${tw`flex-col py-0`}
23 | `;
24 |
25 | const ExpansionPanel = ({ SummaryChild, DetailChild, defaultExpanded, expanded, onChange }) => {
26 | return (
27 |
35 | {SummaryChild}
36 | {DetailChild}
37 |
38 | );
39 | };
40 |
41 | export default ExpansionPanel;
42 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfileShow.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { BooleanInput, Show, SimpleForm, SimpleShowLayout, TextInput } from "react-admin";
3 | import axios from "axios";
4 | import ProfilePage from "./ProfilePage";
5 | import { BACKEND_USERS } from "../../config/backendRoutes";
6 |
7 | const ProfileShowView = props => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | const ProfileShow = props => {
22 | const [profile, setProfile] = useState(null);
23 |
24 | useEffect(() => {
25 | const info = JSON.parse(localStorage.getItem("userInfo"));
26 | const url = `${BACKEND_USERS}${info.userID}`;
27 | axios
28 | .get(url)
29 | .then(res => {})
30 | .catch(err => {});
31 | }, []);
32 | return (
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default ProfileShow;
40 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/courses/CourseFilter.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 | import set from "lodash/set";
6 | // Component Imports
7 | import { Filter } from "react-admin";
8 | import StudentSearchInput from "../../components/Inputs/StudentSearchInput";
9 | import { COURSE } from "../../constants/apiObjects";
10 |
11 | // -------------------------- CONSTANTS --------------------------
12 |
13 | const COURSE_SUBJECT_QUERY = `${COURSE.SUBJECT}_like`;
14 | const COURSE_NUMBER_QUERY = `${COURSE.NUMBER}_like`;
15 | const COURSE_NAME_QUERY = `${COURSE.NAME}_like`;
16 |
17 | // -------------------------- COMPONENT --------------------------
18 |
19 | export const CourseFilter = props => {
20 | // Adds given filter key and value to the redux state filters
21 | const setFilter = (key, val) => {
22 | props.setFilters(set(props.filterValues, key, val));
23 | };
24 |
25 | const searchCourse = event => {
26 | // TODO: implement search here
27 | };
28 |
29 | return (
30 |
31 | `^${inputValue}`} // Regex parameter for start with
35 | onChange={searchCourse}
36 | alwaysOn
37 | />
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/studentDetails/StudentDetailsScreen.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import tw, { styled } from "twin.macro";
4 | import Paper from "@material-ui/core/Paper";
5 |
6 | import { Loading, useGetOne } from "react-admin";
7 | import AppBar from "../../components/Nav/AppBar";
8 |
9 | import StudentDetailsSide from "./StudentDetailsSide";
10 | import StudentDetailsContentGrid from "./StudentDetailsContentGrid";
11 |
12 | const MainContent = styled.div`
13 | ${tw`p-12 pt-2 `}
14 | margin-right: 24.75em;
15 | flex-grow: 1;
16 | `;
17 |
18 | const SideContent = styled(Paper)`
19 | position: fixed;
20 | right: 0;
21 | &.MuiPaper-root {
22 | ${tw`w-99`}
23 | height: 100vh;
24 | }
25 | `;
26 |
27 | const StudentDetailsScreen = props => {
28 | const { data, loading, error } = useGetOne("students", props.match.params.id);
29 | if (loading) {
30 | return ;
31 | }
32 | if (error) {
33 | return Error, id:{id} is not found
;
34 | }
35 | return (
36 | <>
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | >
46 | );
47 | };
48 |
49 | export default StudentDetailsScreen;
50 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/ScoredListField/ScoredListItemSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import Skeleton from "@material-ui/lab/Skeleton";
4 | import ListItem from "@material-ui/core/ListItem";
5 | import ListItemIcon from "@material-ui/core/ListItemIcon";
6 | import ListItemText from "@material-ui/core/ListItemText";
7 | import PropTypes from "prop-types";
8 | import ListItemSecondaryActionMaterial from "@material-ui/core/ListItemSecondaryAction";
9 |
10 | const ListItemSecondaryAction = styled(ListItemSecondaryActionMaterial)`
11 | ${tw`right-1`}
12 | `;
13 |
14 | const ScoredListItemSkeleton = ({ variant = "primary", ...rest }) => {
15 | return (
16 |
17 |
18 |
19 |
20 | }
22 | secondary={variant === "secondary" ? : null}
23 | />
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | ScoredListItemSkeleton.propTypes = {
32 | variant: PropTypes.oneOf(["primary", "secondary"]).isRequired
33 | };
34 |
35 | export default ScoredListItemSkeleton;
36 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Radio Controls/RadioButtonGroup.js:
--------------------------------------------------------------------------------
1 | import React, { cloneElement } from "react";
2 |
3 | import RadioGroup from "@material-ui/core/RadioGroup";
4 | import FormControl from "@material-ui/core/FormControl";
5 | import FormLabel from "@material-ui/core/FormLabel";
6 | import RadioButton from "./RadioButton";
7 |
8 | const RadioButtonsGroup = props => {
9 | const { onChange, label, showLabel, error, children, className } = props;
10 | const [value, setValue] = React.useState("all");
11 |
12 | const handleChange = event => {
13 | setValue(event.target.value);
14 | onChange(event, event.target.value);
15 | };
16 |
17 | return (
18 |
19 | {showLabel && {label} }
20 |
26 | {children &&
27 | children.map((child, index) =>
28 | cloneElement(child, {
29 | key: index,
30 | control:
31 | })
32 | )}
33 |
34 |
35 | );
36 | };
37 |
38 | RadioButtonsGroup.defaultProps = {
39 | showLabel: true
40 | };
41 |
42 | export default RadioButtonsGroup;
43 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/GridCard.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This component is a grid ccaard used for layouts
4 | * TODO:
5 | * - Add heigh adjustment
6 | * Date: 04-23-2020
7 | */
8 |
9 | // -------------------------- IMPORTS --------------------------
10 |
11 | // Function Imports
12 | import React from "react";
13 | import PropTypes from "prop-types";
14 | // Component Imports
15 | import MuiPaper from "@material-ui/core/Paper";
16 | import MuiGrid from "@material-ui/core/Grid";
17 | // Style Imports
18 | import tw, { styled } from "twin.macro";
19 |
20 | // -------------------------- STYLE --------------------------
21 |
22 | const GridItem = styled(MuiGrid)`
23 | ${tw`transition-1`}
24 | `;
25 |
26 | const Paper = styled(MuiPaper)`
27 | ${tw`rounded-xl h-64 w-64 shadow-cardLight`}
28 | `;
29 |
30 | // -------------------------- COMPONENT --------------------------
31 |
32 | const GridCard = (xs, mdFull, mdShrink, isShrunk, { ...props }) => (
33 |
34 | {props.children}
35 |
36 | );
37 |
38 | GridCard.defaultProps = {
39 | xs: 12,
40 | mdFull: 3,
41 | mdShrink: 4,
42 | isShrunk: false
43 | };
44 |
45 | GridCard.propTypes = {
46 | xs: PropTypes.number,
47 | mdFull: PropTypes.number,
48 | mdShrink: PropTypes.number,
49 | isShrunk: PropTypes.bool
50 | };
51 |
52 | export default GridCard;
53 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/ScoredListField/ScoredListItemError.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import ListItem from "@material-ui/core/ListItem";
4 | import ListItemIcon from "@material-ui/core/ListItemIcon";
5 | import ListItemTextMaterial from "@material-ui/core/ListItemText";
6 | import PropTypes from "prop-types";
7 | import ListItemSecondaryActionMaterial from "@material-ui/core/ListItemSecondaryAction";
8 | import StatusBullet from "../../Basic/StatusBullet";
9 |
10 | const ListItemText = styled(ListItemTextMaterial)`
11 | & .MuiListItemText-primary {
12 | ${tw`fontStyle-4 leading-6 font-medium block truncate`}
13 | }
14 |
15 | & .MuiListItemText-secondary {
16 | ${tw`fontStyle-2 text-gray-600`}
17 | }
18 | `;
19 |
20 | const ListItemSecondaryAction = styled(ListItemSecondaryActionMaterial)`
21 | ${tw`fontStyle-4 tracking-wider font-bold text-gray-900 right-1`}
22 | `;
23 |
24 | const ScoredListItemError = ({ errorText, ...rest }) => {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 | 000
32 |
33 | );
34 | };
35 |
36 | ScoredListItemError.propTypes = {
37 | erorText: PropTypes.string
38 | };
39 |
40 | export default ScoredListItemError;
41 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/CourseListField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import { useGetOne } from "react-admin";
4 | import CourseField from "./CourseField";
5 |
6 | const Label = styled.h1`
7 | ${tw`fontStyle-6 font-medium inline-flex`}
8 | font-size: 1.3em;
9 | color: black;
10 | font-weight: bold;
11 | `;
12 |
13 | const Sublabel = styled.h2`
14 | ${tw`fontStyle-6 text-sm inline-flex`}
15 | color: rgba(192, 192, 192, 100);
16 | font-weight: bold;
17 | padding-left: 0.4em;
18 | `;
19 |
20 | const CourseListField = ({ record = {}, source }) => {
21 | const courses = record[source];
22 |
23 | return (
24 |
25 |
26 | Courses
27 | Spring 2020
28 |
29 | {courses.map((course, index) => {
30 | const { data, loading, error } = useGetOne("courses", course.course_id);
31 | if (loading) {
32 | return
loading
;
33 | }
34 | if (error) {
35 | console.log("ERROR", error);
36 | return
ERROR
;
37 | }
38 | if (index === courses.length - 1) {
39 | return
;
40 | }
41 |
42 | return
;
43 | })}
44 |
45 | );
46 | };
47 |
48 | export default CourseListField;
49 |
--------------------------------------------------------------------------------
/pharmd-app/src/constants/apiObjects.js:
--------------------------------------------------------------------------------
1 | export const STUDENT = {
2 | NEU_ID: "id",
3 | NAME: "name",
4 | FIRST_NAME: "firstName",
5 | LAST_NAME: "lastName",
6 | COHORT: "gradDate",
7 | STATUS: "status",
8 | GPA: "gpa"
9 | };
10 |
11 | export const STUDENT_STATUS = {
12 | ENROLLED: "ENROLLED",
13 | LEAVE: "LEAVE",
14 | DROP_BACK: "DROP_BACK",
15 | COOP: "COOP",
16 | GRADUATED: "GRADUATED"
17 | };
18 |
19 | export const NOTE = {
20 | ID: "id",
21 | TITLE: "title",
22 | BODY: "body",
23 | TAGS: "tags",
24 | DATE: "date"
25 | };
26 |
27 | export const COURSE = {
28 | SUBJECT: "subject",
29 | NUMBER: "number",
30 | NAME: "name"
31 | };
32 |
33 | export const STUDENT_COURSE = {
34 | SEMESTER: "semester",
35 | YEAR: "year",
36 | PERCENTAGE: "percentage",
37 | LETTER_GRADE: "letterGrade",
38 | COURSE_SUBJECT: "course.subject",
39 | COURSE_NUMBER: "course.number",
40 | COURSE_NAME: "course.name"
41 | };
42 |
43 | export const STUDENT_EXAM = {
44 | SEMESTER: "semester",
45 | YEAR: "year",
46 | PERCENTAGE: "percentage",
47 | LETTER_GRADE: "letterGrade",
48 | EXAM_NAME: "exam.name",
49 | COURSE_SUBJECT: "exam.course.subject",
50 | COURSE_NUMBER: "exam.course.number",
51 | COURSE_NAME: "exam.course.name"
52 | };
53 |
54 | export const LETTER_GRADE = {
55 | A: "A",
56 | A_MINUS: "A-",
57 | B_PLUS: "B+",
58 | B: "B",
59 | B_MINUS: "B-",
60 | C_PLUS: "C+",
61 | C: "C",
62 | C_MINUS: "C-",
63 | D_PLUS: "D+",
64 | D: "D",
65 | D_MINUS: "D-",
66 | F: "F"
67 | };
68 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Inputs/StudentSearchInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * Redux cccconnected Search Box Input.
4 | * To be used inside a component
5 | * TODO:
6 | * -- Use Basic TextField Instead
7 | * Date: 04-24-2020
8 | */
9 | // -------------------------- IMPORTS --------------------------
10 |
11 | // Function Imports
12 | import React from "react";
13 | import { useInput } from "react-admin";
14 | import PropTypes from "prop-types";
15 | // Style Imports
16 | import tw, { styled } from "twin.macro";
17 | // Component Imports
18 | import SearchInput from "./SearchInput";
19 |
20 | // -------------------------- STYLE --------------------------
21 | const SearchBox = styled(SearchInput)`
22 | ${tw`rounded-xl shadow-none p-2 bg-background`}
23 | input {
24 | ${tw`bg-background fontStyle-6`}
25 | }
26 | `;
27 |
28 | // -------------------------- COMPONENT --------------------------
29 | const StudentSearchInput = props => {
30 | const {
31 | input: { name, onChange },
32 | meta: { touched, error },
33 | isRequired
34 | } = useInput(props);
35 |
36 | return (
37 |
44 | );
45 | };
46 |
47 | StudentSearchInput.defaultProps = {};
48 |
49 | StudentSearchInput.propTypes = {
50 | label: PropTypes.string.isRequired,
51 | onChange: PropTypes.func.isRequired
52 | };
53 |
54 | export default StudentSearchInput;
55 |
--------------------------------------------------------------------------------
/pharmd-app/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./styles/App.css";
3 | import { Admin, Resource } from "react-admin";
4 | import { ThemeConsumer, ThemeProvider } from "styled-components";
5 | import students from "./screens/student/index.js";
6 | import upload from "./screens/upload/index.js";
7 | import profile from "./screens/profile/index.js";
8 | import courses from "./screens/courses";
9 | import Dashboard from "./Dashboard";
10 | import { AuthProvider, DataProvider } from "./services";
11 | import createLigthTheme from "./themes/light-theme";
12 | import DashboardLayout from "./components/Layout/DashboardLayout";
13 | import customRoutes from "./config/customRoutes";
14 |
15 | const App = () => {
16 | return (
17 |
18 |
19 | {theme => (
20 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | )}
36 |
37 |
38 | );
39 | };
40 |
41 | export default App;
42 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | This component contains a DataGrid that takes in field components for children.
4 | The componnet manages dispatching the data (resource) to each field and the source (data key).
5 | */
6 |
7 | // -------------------------- IMPORTS --------------------------
8 |
9 | // Function Imports
10 | import React from "react";
11 |
12 |
13 | // Component Imports
14 | import Table from "../../components/Table/Table";
15 | import EmphasisField from "../../components/Fields/EmphasisField";
16 | import TextField from "../../components/Fields/TextField";
17 | import ChipField from "../../components/Fields/ChipField";
18 | import GradeField from "../../components/Fields/GradeField";
19 | import { STUDENT } from "../../constants/apiObjects";
20 |
21 | const StudentList = ({ selectedRow, ...props }) => {
22 |
23 | const studentRowClick = (id, basePath, record) => {
24 | props.setStudentSidebar(true);
25 | props.setStudentQuickViewExpanded(true);
26 | return record.editable ? "edit" : "show";
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 | export default StudentList;
41 |
--------------------------------------------------------------------------------
/pharmd-app/src/styles/App.css:
--------------------------------------------------------------------------------
1 | /* Material UI CSS Variables */
2 |
3 | :root {
4 | --color-bg-primary: var(--background);
5 | --color-bg-card: var(--white);
6 | --color-bg-tooltip: var(--gray-3);
7 | --color-primary: var(--primary);
8 | --color-secondary: var(--secondary);
9 | --color-tertiary: var(--tertiary);
10 |
11 | --color-text-primary: var(--gray-1);
12 | --color-text-secondary: var(--gray-3);
13 | --color-text-tertiary: var(--gray-4);
14 | --color-text-emphasis-high: var(--primary);
15 | --color-text-emphasis-low: var(--secondary);
16 | --color-text-inverse: var(--white);
17 |
18 | --color-red: var(--red);
19 | --color-orange: var(--orange);
20 | --color-yellow: var(--yellow);
21 | --color-green: var(--green);
22 | }
23 |
24 | /*! purgecss start ignore */
25 | .theme-dark {
26 | --color-bg-primary: var(--palette-text);
27 | --color-bg-secondary: #dcd0c0;
28 | --color-bg-default: #f4f4f4;
29 | --color-bg-inverse: #373737;
30 |
31 | --color-text-primary: #fff;
32 | --color-text-secondary: #927e7e;
33 | --color-text-default: #373737;
34 | --color-text-default-soft: #6a6a6a;
35 | --color-text-inverse: #fff;
36 | --color-text-inverse-soft: #bbb;
37 |
38 | --font-display: Georgia, Times, "Times New Roman", serif;
39 | --font-body: Corbel, "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", "DejaVu Sans",
40 | "Bitstream Vera Sans", "Liberation Sans", Verdana, "Verdana Ref", sans-serif;
41 |
42 | --font-weight-normal: 400;
43 | --font-weight-display: 400;
44 | --font-weight-btn: 600;
45 |
46 | --rounded-btn: 0;
47 | }
48 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Layout/DashboardLayout.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React from "react";
5 |
6 | // Style Imports
7 | import tw from "twin.macro";
8 | import { makeStyles } from "@material-ui/core/styles";
9 | import { Layout, Notification, Sidebar } from "react-admin";
10 | import Menu from "./Menu";
11 |
12 | const MyAppBar = props => <>>;
13 |
14 | const useSidebarStyles = makeStyles({
15 | drawerPaper: {
16 | backgroundColor: "white",
17 | height: "100%",
18 | width: "96px",
19 | marginTop: "0px",
20 | position: "fixed",
21 | boxShadow:
22 | "rgba(0, 0, 0, 0.05) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 13px, rgba(0, 0, 0, 0.03) 0px 3px 10px"
23 | }
24 | });
25 |
26 | const useLayoutStyles = makeStyles({
27 | appFrame: {
28 | marginTop: "0px",
29 | flexDirection: "row"
30 | },
31 | contentWithSidebar: {
32 | display: "flex",
33 | flexGrow: 1
34 | },
35 | content: {
36 | display: "flex",
37 | flexDirection: "row",
38 | padding: 0
39 | }
40 | });
41 |
42 | const MySidebar = props => {
43 | const classes = useSidebarStyles();
44 | return ;
45 | };
46 |
47 | const DashboardLayout = props => {
48 | return (
49 |
57 | );
58 | };
59 |
60 | export default DashboardLayout;
61 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/tableCells.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { select, text, withKnobs } from "@storybook/addon-knobs";
3 | import { withDesign } from "storybook-addon-designs";
4 | import EmphasisField from "../components/Fields/EmphasisField";
5 | import ChipField from "../components/Fields/ChipField";
6 |
7 | export default {
8 | title: "Table Cells",
9 | component: ChipField,
10 | decorators: [withKnobs, withDesign, storyFn => {storyFn()}
]
11 | };
12 |
13 | const label = "Status";
14 | const options = {
15 | Enrolled: "enrolled",
16 | Dropback: "dropback",
17 | Leave: "leave",
18 | Gradutated: "graduated",
19 | Coop: "coop"
20 | };
21 | const defaultValue = "enrolled";
22 | const groupId = "GROUP-ID1";
23 |
24 | const labelText = "Cell Text";
25 | const defaultLabel = "BOLD TEXT";
26 |
27 | export const StatusChip = () => (
28 |
32 | );
33 |
34 | StatusChip.story = {
35 | parameters: {
36 | design: {
37 | type: "figma",
38 | url:
39 | "https://www.figma.com/file/QBWcVYJkF6K8o7WErRPsxd/Student-List-Mockups?node-id=806%3A0"
40 | }
41 | }
42 | };
43 |
44 | export const EmphasisCell = () => (
45 |
46 | );
47 |
48 | EmphasisCell.story = {
49 | parameters: {
50 | design: {
51 | type: "figma",
52 | url:
53 | "https://www.figma.com/file/QBWcVYJkF6K8o7WErRPsxd/Student-List-Mockups?node-id=31%3A40"
54 | }
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Nav/AppBar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a transparent app bar used to display the
4 | * current user location. It also provides the ability to use
5 | * breadcrumbs to navigate to previus locations
6 | */
7 |
8 | // -------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React from "react";
12 | import PropTypes from "prop-types";
13 | // Style Imports
14 | import tw, { styled } from "twin.macro";
15 | // Component Imports
16 | import MuiAppBar from "@material-ui/core/AppBar";
17 | import MuiToolbar from "@material-ui/core/Toolbar";
18 | import RouterBreadcrumb from "../Basic/Breadcrumb";
19 |
20 | // -------------------------- STYLE --------------------------
21 | const AppBarStyled = styled(MuiAppBar)`
22 | ${tw`shadow-none h-24`}
23 | `;
24 |
25 | const Title = styled.p`
26 | ${tw`fontStyle-10 m-0 text-gray-700`}
27 | `;
28 |
29 | const Toolbar = styled(MuiToolbar)`
30 | ${tw`absolute bottom-0`}
31 | `;
32 |
33 | // -------------------------- COMPONENT --------------------------
34 | const AppBar = ({ title, location, children, props }) => (
35 |
36 |
37 |
38 |
39 |
{title}
40 |
41 | {children}
42 |
43 |
44 | );
45 |
46 | AppBar.defaultProps = {};
47 |
48 | AppBar.propTypes = {
49 | title: PropTypes.string.isRequired,
50 | location: PropTypes.string,
51 | children: PropTypes.element
52 | };
53 |
54 | export default AppBar;
55 |
--------------------------------------------------------------------------------
/pharmd-app/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebPackPlugin = require("html-webpack-plugin");
2 | const webpack = require("webpack");
3 | const dotenv = require("dotenv");
4 |
5 | module.exports = () => {
6 | const env = dotenv.config().parsed;
7 |
8 | // reduce it to an object
9 | const envKeys = Object.keys(env).reduce((prev, next) => {
10 | prev[`process.env.${next}`] = JSON.stringify(env[next]);
11 | return prev;
12 | }, {});
13 |
14 | return {
15 | entry: "./src/index.js",
16 | resolve: { symlinks: false },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js?$/,
21 | exclude: /node_modules/,
22 | use: {
23 | loader: "babel-loader"
24 | }
25 | },
26 | {
27 | test: /\.css$/i,
28 | use: ["style-loader", "css-loader"]
29 | },
30 | {
31 | test: /\.svg$/,
32 | use: ["@svgr/webpack"]
33 | },
34 | {
35 | test: /\.(jpg|png)$/,
36 | use: {
37 | loader: "url-loader"
38 | }
39 | }
40 | ]
41 | },
42 | plugins: [
43 | new HtmlWebPackPlugin({
44 | template: "./src/index.html",
45 | filename: "./index.html"
46 | }),
47 | new webpack.DefinePlugin(envKeys)
48 | ],
49 | performance: { hints: false },
50 | target: "web",
51 | node: {
52 | fs: "empty"
53 | },
54 | devServer: {
55 | stats: "errors-only",
56 | open: true,
57 | hot: true,
58 | watchOptions: {
59 | poll: 500,
60 | ignored: ["node_modules/**", ".git/**"]
61 | }
62 | }
63 | };
64 | };
65 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/QuickProfileField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Loading, TextField, useGetOne } from "react-admin";
3 |
4 | import tw, { styled } from "twin.macro";
5 | import AvatarField from "./AvatarField";
6 | import QuickChipField from "./QuickChipField";
7 |
8 | const Profile = styled.section`
9 | display: block;
10 | text-align: center;
11 | margin-left: auto;
12 | margin-right: auto;
13 | `;
14 |
15 | const NameField = styled(TextField)`
16 | display: block;
17 | font-size: 2em;
18 | font-weight: bold;
19 | color: #2b2b90;
20 | `;
21 |
22 | const IdField = styled(TextField)`
23 | display: block;
24 | font-size: 1em;
25 | color: ${props => props.theme.palette.text.secondary};
26 | `;
27 |
28 | const StatusField = styled.span`
29 | ${tw` w-28 `}
30 | display: block;
31 | margin-right: auto;
32 | margin-bottom: 15px;
33 | `;
34 |
35 | const PictureField = styled(AvatarField)`
36 | display: block;
37 | `;
38 |
39 | const QuickProfileField = ({ record = {}, source }) => {
40 | const id = record[source];
41 | const { data, loading, error } = useGetOne("students", id);
42 | if (loading) {
43 | return ;
44 | }
45 | if (error) {
46 | return Error, id:{id} is not found
;
47 | }
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default QuickProfileField;
62 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Auth/login.js:
--------------------------------------------------------------------------------
1 | import decodeJwt from "jwt-decode";
2 | import { FAKE_AUTH } from "../../config/backendRoutes";
3 |
4 | // import AUTH_URL instead to use real pharmd data
5 | const AUTH_URL = FAKE_AUTH;
6 |
7 | /*
8 | react-admin enables a new page on the /login route,
9 | which displays a login form asking for a username and password.
10 | Upon submission, this form calls this authProvider.login({ login, password }) method.
11 | It’s the ideal place to authenticate the user, and store their credentials.
12 | */
13 |
14 | /**
15 | * Called when the user submits the login form. A good place to authenticate
16 | * @param {string} username
17 | * @param {string} password
18 | * @return {Promise}
19 | */
20 | export default ({ username, password }) => {
21 | const request = new Request(AUTH_URL, {
22 | method: "POST",
23 | body: JSON.stringify({ email: username, password }),
24 | headers: new Headers({ "Content-Type": "application/json" })
25 | });
26 | return fetch(request)
27 | .then(response => {
28 | if (response.status < 200 || response.status >= 300) {
29 | return Promise.reject(response.statusText);
30 | }
31 | return response.json();
32 | })
33 | .then(({ accessToken }) => {
34 | const decodedToken = decodeJwt(accessToken);
35 | localStorage.setItem("token", accessToken);
36 | localStorage.setItem(
37 | "userInfo",
38 | JSON.stringify({
39 | firstName: decodedToken.firstName,
40 | lastName: decodedToken.lastName,
41 | userID: decodedToken.userID
42 | })
43 | );
44 | localStorage.setItem(
45 | "permissions",
46 | decodedToken.isAdmin || decodedToken.canWrite || decodedToken.email === "kevin@mail.com"
47 | ? "admin"
48 | : "user"
49 | );
50 | })
51 | .catch(err => {});
52 | };
53 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Inputs/SearchInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description: A basic no logicc search box component
3 | * TODO:
4 | * -- Abstract the component: Potentially make this searchbox into a generic text field that takes in an icon.
5 | * -- Use Tailwind classes
6 | * -- Create more generic search fields
7 | * Date: 04-24-2020
8 | */
9 |
10 | // -------------------------- IMPORTS --------------------------
11 |
12 | // Function Imports
13 | import React from "react";
14 | import PropTypes from "prop-types";
15 | import clsx from "clsx";
16 | // Style Imports
17 | import { styled } from "twin.macro";
18 | // Component Imports
19 | import MuiInput from "@material-ui/core/Input";
20 | import MuiPaper from "@material-ui/core/Paper";
21 | import MuiSearchIcon from "@material-ui/icons/Search";
22 |
23 | // -------------------------- STYLE --------------------------
24 | const Paper = styled(MuiPaper)`
25 | border-radius: 4px;
26 | align-items: center;
27 | padding: ${props => `${props.theme.spacing(1)}px`};
28 | display: flex;
29 | flex-basis: 420;
30 | `;
31 |
32 | const Input = styled(MuiInput)`
33 | flex-grow: 1;
34 | font-size: 14px;
35 | line-height: 16px;
36 | letter-spacing: -0.05px;
37 | `;
38 |
39 | const SearchIcon = styled(MuiSearchIcon)`
40 | margin-right: ${props => `${props.theme.spacing(1)}px`};
41 | color: ${props => props.theme.palette.text.secondary};
42 | `;
43 |
44 | // -------------------------- COMPONENT --------------------------
45 | const SearchInput = props => {
46 | const { className, onChange, style, ...rest } = props;
47 |
48 | return (
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | SearchInput.defaultProps = {};
57 |
58 | SearchInput.propTypes = {
59 | className: PropTypes.string,
60 | onChange: PropTypes.func,
61 | style: PropTypes.object
62 | };
63 |
64 | export default SearchInput;
65 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/upload/components/UploadFileChooser.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react";
2 | import FileUploadService from "../../../services/FileUploadService";
3 | import Button from '../../../components/Form/Button';
4 | import FileSelector from '../../../components/Form/FileSelector';
5 | import PropTypes from 'prop-types';
6 |
7 | /**
8 | * @class UploadFileChooser a component for choosing a file to upload
9 | */
10 | class UploadFileChooser extends Component {
11 | static propTypes = {
12 | uploadedFileData: PropTypes.func
13 | };
14 |
15 | static defaultProps = {
16 | uploadedFileData() {}
17 | };
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | selectedFile: null,
23 | uploadedFile: {},
24 | fileName: "",
25 | uploadMessage: "",
26 | uploadProgress: 0
27 | };
28 |
29 | this.chooseFileHandler = this.chooseFileHandler.bind(this);
30 | this.uploadFileHandler = this.uploadFileHandler.bind(this);
31 | }
32 |
33 | /**
34 | * Executes when a file is chosen by the user
35 | * @param {File} file
36 | */
37 | chooseFileHandler(file) {
38 | this.setState({
39 | selectedFile: file,
40 | fileName: file.name
41 | });
42 | }
43 |
44 | async uploadFileHandler(event) {
45 | event.preventDefault();
46 | const spreadsheetData = await FileUploadService.loadSpreadsheet(this.state.selectedFile);
47 | this.props.uploadedFileData(spreadsheetData);
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
58 |
59 |
60 | {this.state.selectedFile &&
61 |
62 | Upload
63 |
64 | }
65 |
66 | );
67 | }
68 | }
69 |
70 | export default UploadFileChooser;
71 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/StatusBullet.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import clsx from "clsx";
4 | import { makeStyles } from "@material-ui/styles";
5 |
6 | const useStyles = makeStyles(theme => ({
7 | root: {
8 | display: "inline-block",
9 | borderRadius: "50%",
10 | flexGrow: 0,
11 | flexShrink: 0
12 | },
13 | sm: {
14 | height: theme.spacing(1),
15 | width: theme.spacing(1)
16 | },
17 | md: {
18 | height: theme.spacing(2),
19 | width: theme.spacing(2)
20 | },
21 | lg: {
22 | height: theme.spacing(3),
23 | width: theme.spacing(3)
24 | },
25 | neutral: {
26 | backgroundColor: theme.palette.neutral.main
27 | },
28 | primary: {
29 | backgroundColor: theme.palette.primary.main
30 | },
31 | info: {
32 | backgroundColor: theme.palette.info.main
33 | },
34 | warning: {
35 | backgroundColor: theme.palette.warning.main
36 | },
37 | danger: {
38 | backgroundColor: theme.palette.error.main
39 | },
40 | good: {
41 | backgroundColor: theme.palette.good.main
42 | },
43 | success: {
44 | backgroundColor: theme.palette.success.main
45 | }
46 | }));
47 |
48 | const StatusBullet = props => {
49 | const { className, size, color, ...rest } = props;
50 |
51 | const classes = useStyles();
52 |
53 | return (
54 |
65 | );
66 | };
67 |
68 | StatusBullet.propTypes = {
69 | className: PropTypes.string,
70 | color: PropTypes.oneOf([
71 | "neutral",
72 | "primary",
73 | "info",
74 | "good",
75 | "success",
76 | "warning",
77 | "danger"
78 | ]),
79 | size: PropTypes.oneOf(["sm", "md", "lg"])
80 | };
81 |
82 | StatusBullet.defaultProps = {
83 | size: "md",
84 | color: "default"
85 | };
86 |
87 | export default StatusBullet;
88 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/student.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentToolbarFilter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * Component manaages table toolbar filters.
4 | * Currently we only display a seaarh filter.
5 | * TODO:
6 | * - Add more filters if needed: A filter can be added
7 | * as a child of and might require a custom
8 | * onChange and parse filter function to communicate with the api.
9 | * Date: 04-24-2020
10 | */
11 | // -------------------------- IMPORTS --------------------------
12 |
13 | // Function Imports
14 | import React from "react";
15 | import set from "lodash/set";
16 | // Component Imports
17 | import { Filter } from "react-admin";
18 | import StudentSearchInput from "../../components/Inputs/StudentSearchInput";
19 | import { STUDENT } from "../../constants/apiObjects";
20 |
21 | const STUDENT_NAME_QUERY = `${STUDENT.NAME}_like`;
22 | const STUDENT_ID_QUERY = `${STUDENT.NEU_ID}_like`;
23 |
24 | // -------------------------- COMPONENT --------------------------
25 |
26 | export const StudentFilter = props => {
27 | // Adds given filter key and value to the redux state filters
28 | const setFilter = (key, val) => {
29 | props.setFilters(set(props.filterValues, key, val));
30 | };
31 |
32 | // Fuunction that manages how a student is searched based on input event
33 | const searchStudent = event => {
34 | const val = event.target.value;
35 | if (val === "") {
36 | delete props.filterValues[STUDENT_NAME_QUERY];
37 | delete props.filterValues[STUDENT_ID_QUERY];
38 | props.setFilters(props.filterValues);
39 | } else {
40 | setFilter(
41 | isNaN(val) ? STUDENT_NAME_QUERY : STUDENT_ID_QUERY,
42 | isNaN(val) ? val : `^${val}`
43 | );
44 | }
45 | };
46 |
47 | return (
48 |
49 | `^${inputValue}`} // Regex parameter for start with
53 | onChange={searchStudent}
54 | alwaysOn
55 | />
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Layout/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { useSelector } from "react-redux";
3 | import { getResources, usePermissions } from "react-admin";
4 | import { withRouter } from "react-router-dom";
5 | import Divider from "@material-ui/core/Divider";
6 |
7 | import NavItemLink from "../Nav/NavItemLink";
8 | import NavIndicator from "../Nav/NavIndicator";
9 |
10 | const Menu = ({ onMenuClick, logout, ...props }) => {
11 | const location = props.location.pathname;
12 | const { permissions } = usePermissions();
13 | const hiddenResources = ["studentCourses", "studentExams"];
14 | let resources = useSelector(getResources).filter(val => !hiddenResources.includes(val.name));
15 | if (permissions === "user") {
16 | resources = resources.filter(val => val.name !== "upload");
17 | }
18 | const flat = resources.reduce((total, val) => total.concat(val.name), []);
19 |
20 | const getCurrentIndex = () => {
21 | // TEMPORARY SOLUTION WILL FIND A BETTER ONE THAT RELOADS ONLY IF NEEDE
22 | const currentIndex = flat.indexOf(location.split("/")[1]);
23 | return currentIndex;
24 | };
25 |
26 | return (
27 |
28 | {resources.map((resource, index) => {
29 | const isActive = location.includes(resource.name);
30 | const linkProps = {
31 | key: resource.name || index,
32 | to: `/${resource.name}`,
33 | title: (resource.options && resource.options.label) || resource.name,
34 | iconSrc: resource.icon,
35 | onClick: onMenuClick,
36 | sidebarIsOpen: false,
37 | isActive
38 | };
39 | if (index === 0) {
40 | return (
41 |
42 |
43 |
44 |
45 | );
46 | }
47 | return
;
48 | })}
49 |
50 |
51 | {logout}
52 |
53 | );
54 | };
55 |
56 | export default withRouter(Menu);
57 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Form/Select.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {FormControl, InputLabel, MenuItem, Select} from '@material-ui/core';
3 | import PropTypes from 'prop-types';
4 | import tw, {styled} from "twin.macro";
5 |
6 | const StyledFormControl = styled(FormControl)`
7 | ${tw`m-4`};
8 | min-width: 12rem;
9 | `;
10 |
11 | /**
12 | * @class CustomSelect a component for creating an easy select menu
13 | */
14 | class CustomSelect extends React.Component {
15 | static propTypes = {
16 | title: PropTypes.string,
17 | onChange: PropTypes.func,
18 | selectItems: PropTypes.arrayOf(PropTypes.shape({
19 | value: PropTypes.any,
20 | displayValue: PropTypes.string
21 | }))
22 | };
23 |
24 | static defaultProps = {
25 | title: '',
26 | onChange() {},
27 | selectItems: []
28 | };
29 |
30 | constructor(props) {
31 | super(props);
32 | this.state = {
33 | currentValue: ''
34 | };
35 | this.handleOnChange = this.handleOnChange.bind(this);
36 | }
37 |
38 | handleOnChange($event) {
39 | const newVal = $event.target.value;
40 | this.setState({
41 | currentValue: newVal
42 | });
43 | this.props.onChange(newVal);
44 | }
45 |
46 |
47 | render() {
48 | return (
49 |
50 | {this.props.title}
51 |
58 | {this.props.selectItems.map((item, index) => (
59 | {item.displayValue}
60 | ))}
61 |
62 |
63 | );
64 | }
65 |
66 | }
67 |
68 | export default CustomSelect;
69 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/NoteField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import AccessTimeIcon from "@material-ui/icons/AccessTimeOutlined";
4 | import EditIcon from "@material-ui/icons/EditOutlined";
5 | import NoteIcon from "../Basic/NoteIcon";
6 | import { NOTE } from "../../constants/apiObjects";
7 |
8 | const Info = styled.div`
9 | ${tw`fontStyle-6 text-black font-medium`}
10 | width: 100%;
11 | padding-bottom: 10px;
12 | font-size: 0.89em;
13 | display: grid;
14 | grid-template-columns: auto;
15 | `;
16 |
17 | const Heading = styled.div`
18 | margin-top: 1em;
19 | align-items: center;
20 | display: inline-flex;
21 | height: auto;
22 | `;
23 |
24 | const Time = styled.div`
25 | align-items: center;
26 | display: inline-flex;
27 | height: auto;
28 | `;
29 |
30 | const Title = styled.h3`
31 | margin: 0px;
32 | font-size: 1.2em;
33 | height: auto;
34 | `;
35 |
36 | const DateLabel = styled.h4`
37 | font-weight: normal;
38 | font-size: 0.85em;
39 | margin: 0px 0px 0px 0.5em;
40 | color: ${props => props.theme.palette.tertiary.main};
41 | `;
42 |
43 | const Content = styled.div`
44 | height: 3em;
45 | line-height: 1em;
46 | overflow: hidden;
47 | margin-top: 0.4em;
48 | word-break: break-all;
49 | `;
50 |
51 | const NoteField = ({ record, source }) => {
52 | const title = record[NOTE.TITLE];
53 | const body = record[NOTE.BODY];
54 | const date = new Date(record[NOTE.DATE]).toLocaleString();
55 |
56 | return (
57 |
58 |
59 | {title}
60 |
61 |
62 |
63 |
64 | {date}
65 |
66 | {/* if the length of string is more than 2 lines - ask jose how to check for this */}
67 |
68 | {body}
69 |
70 | {/* add a contional where if 3 or more... */}
71 | {/* use QuickChipField for the chip component */}
72 |
73 | );
74 | };
75 |
76 | export default NoteField;
77 |
--------------------------------------------------------------------------------
/pharmd-app/src/themes/light-theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from "@material-ui/core/styles";
2 |
3 | const STYLE = getComputedStyle(document.body);
4 | function getValue(cssVariable) {
5 | return STYLE.getPropertyValue(cssVariable).trim();
6 | }
7 |
8 | export const FIELD_COLOR_NAMES = {
9 | PRIMARY: "PRIMARY",
10 | GREEN: "GREEN",
11 | RED: "RED",
12 | ORANGE: "ORANGE",
13 | TERTIARY: "TERTIARY",
14 | DEFAULT: "DEFAULT"
15 | };
16 |
17 | export const FIELD_COLOR_VALUES = {
18 | [FIELD_COLOR_NAMES.PRIMARY]: getValue("--primary"),
19 | [FIELD_COLOR_NAMES.GREEN]: getValue("--green-2"),
20 | [FIELD_COLOR_NAMES.RED]: getValue("--red"),
21 | [FIELD_COLOR_NAMES.ORANGE]: getValue("--orange"),
22 | [FIELD_COLOR_NAMES.TERTIARY]: getValue("--tertiary"),
23 | [FIELD_COLOR_NAMES.DEFAULT]: getValue("--gray-3")
24 | };
25 |
26 | function createLightTheme() {
27 | const overrides = {
28 | name: "Light Theme",
29 | palette: {
30 | fieldColors: {
31 | ...FIELD_COLOR_VALUES
32 | },
33 | primary: {
34 | main: getValue("--primary"),
35 | contrastText: getValue("--white")
36 | },
37 | secondary: {
38 | main: getValue("--secondary"),
39 | contrastText: getValue("--white")
40 | },
41 | tertiary: {
42 | main: getValue("--tertiary"),
43 | contrastText: getValue("--white")
44 | },
45 | error: {
46 | main: getValue("--red")
47 | },
48 | warning: {
49 | main: getValue("--orange")
50 | },
51 | info: {
52 | main: getValue("--primary")
53 | },
54 | good: {
55 | main: getValue("--yellow")
56 | },
57 | success: {
58 | main: getValue("--green-2")
59 | },
60 | neutral: {
61 | main: getValue("--gray-3")
62 | },
63 | text: {
64 | primary: getValue("--gray-1"),
65 | secondary: getValue("--gray-3"),
66 | hint: getValue("--gray-4")
67 | },
68 | background: {
69 | paper: getValue("--white"),
70 | default: getValue("--background")
71 | }
72 | }
73 | };
74 | return createMuiTheme(overrides);
75 | }
76 |
77 | export default createLightTheme;
78 |
--------------------------------------------------------------------------------
/pharmd-app/src/assets/icons/person.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pharmd-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
20 |
21 |
25 |
26 |
35 | React App
36 |
37 |
38 | You need to enable JavaScript to run this app.
39 |
40 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Checkbox Controls/CheckboxFilterButtonGroup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a group of filter checkbox buttons.
4 | * Has the same functionality as CheckboxButtonGroup component, but checkboxes apply filters
5 | * when checked.
6 | *
7 | * Date: 02-18-2021
8 | */
9 |
10 | // -------------------------- IMPORTS --------------------------
11 |
12 | // Function Imports
13 | import React from "react";
14 |
15 | // Component Imports
16 | import CheckboxButtonGroup from "./CheckboxButtonGroup";
17 |
18 | // -------------------------- COMPONENT --------------------------
19 | const CheckboxFilterButtonGroup = props => {
20 | const {
21 | checkboxCheckedClass,
22 | checkboxClassName,
23 | children,
24 | className,
25 | color,
26 | error,
27 | formGroupClassName,
28 | label,
29 | onChange,
30 | showLabel,
31 | } = props;
32 |
33 | const [filters, setFilters] = React.useState([]);
34 |
35 | // Keeps tracks of filters that have been applied and gives onChange function an updated array
36 | // of filters along with the event (when the checkbox was checked/unchecked).
37 | const handleChange = event => {
38 | const filter = event.target.value;
39 | const filterIndex = filters.indexOf(filter);
40 | if (event.target.checked) {
41 | if (filterIndex < 0) {
42 | // If the checkbox was checked and the value is not in our array of filter values then add the value to the array
43 | filters.push(filter);
44 | }
45 | } else if (filterIndex >= 0) {
46 | // If the checkbox was unchecked and the value is in our array of filter values then remove the value from the array
47 | filters.splice(filterIndex, 1);
48 | }
49 | // Finally, update the filter values and call the onChange function with the new list
50 | setFilters(filters);
51 | onChange(event, filters);
52 | };
53 |
54 | return (
55 |
67 | );
68 | };
69 |
70 | export default CheckboxFilterButtonGroup;
71 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentContentGrid.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This component determines the main layout of the StudentDetails Screen
4 | * TODO:
5 | * - Add detail cards
6 | * Date: 04-23-2020
7 | */
8 |
9 | // -------------------------- IMPORTS --------------------------
10 |
11 | // Function Imports
12 | import React from "react";
13 | import { useSelector } from "react-redux";
14 | import PropTypes from "prop-types";
15 | // Component Imports
16 | import { List as RaList } from "react-admin";
17 | import MuiGrid from "@material-ui/core/Grid";
18 | // Style Imports
19 | import tw, { styled } from "twin.macro";
20 | import { StudentFilter } from "./StudentToolbarFilter";
21 | import StudentList from "./StudentList";
22 | import GridCard from "../../components/Basic/GridCard";
23 | import StudenttDrawer from "./StudentDrawer";
24 |
25 | // -------------------------- STYLE --------------------------
26 | const List = styled(RaList)`
27 | ${tw`p-6 rounded-xl bg-white shadow-cardLight`}
28 |
29 | .content {
30 | ${tw`shadow-none`}
31 | }
32 | `;
33 |
34 | const MainGrid = styled(MuiGrid)`
35 | ${tw`pt-12 `}
36 | `;
37 |
38 | // -------------------------- COMPONENT --------------------------
39 | const StudentContentGrid = ({ selected, ...props }) => {
40 | const isOpen = props.StudentSidebar;
41 |
42 | return (
43 |
44 |
45 | {[...Array(3)].map((e, i) => (
46 |
47 | {`GridItem-${i}`}
48 |
49 | ))}
50 |
51 |
52 |
}
54 | bulkActionButtons={false}
55 | {...props}
56 | classes={{ content: "content" }}
57 | >
58 |
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | StudentContentGrid.defaultProps = {};
72 |
73 | StudentContentGrid.propTypes = {
74 | selected: PropTypes.any
75 | };
76 |
77 | export default StudentContentGrid;
78 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentResource.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * TODO:\Creates the main students screen
4 | * FIXME: Some Boolean logic error doesn't allow the quickview
5 | * sidebar to open when a table is clicked for the first time
6 | * Date: 04-23-2020$
7 | */
8 | // -------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React, { useCallback, useState } from "react";
12 | import { Route, useHistory } from "react-router-dom";
13 | // import { useDispatch } from "react-redux";
14 | import tw, { styled } from "twin.macro";
15 | // import { setStudentSideBar } from "../../redux/actions";
16 | import { STUDENT_QUICKVIEW, STUDENTS_MAIN } from "../../constants/routes";
17 | // Component Imports
18 | import AppBar from "../../components/Nav/AppBar";
19 | import StudentContentGrid from "./StudentContentGrid";
20 | import StudentDrawer from "./StudentDrawer";
21 |
22 | // -------------------------- STYLE --------------------------
23 | const MainContent = styled.div`
24 | ${tw`p-12 pt-2 flex-grow`}
25 | `;
26 |
27 | // -------------------------- COMPONENT --------------------------
28 | const StudentScreen = props => {
29 |
30 | const [studentSidebar, setStudentSidebar] = useState(true);
31 | const [studentQuickViewExpanded, setStudentQuickViewExpanded] = useState(false);
32 |
33 | return (
34 |
35 | {({ match }) => {
36 | const isMatch = match && match.params && match.params.id !== "create"; // has the user selected a new student
37 |
38 | return (
39 | <>
40 |
41 |
42 |
51 |
52 |
53 |
62 | >
63 | );
64 | }}
65 |
66 | );
67 | };
68 |
69 | export default StudentScreen;
70 |
--------------------------------------------------------------------------------
/pharmd-app/src/stories/table.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Table from "@material-ui/core/Table";
3 | import TableBody from "@material-ui/core/TableBody";
4 | import TableHead from "@material-ui/core/TableHead";
5 | import { TextField } from "react-admin";
6 | import { withDesign } from "storybook-addon-designs";
7 | import { FieldTitle } from "ra-core";
8 | import TableRow from "../components/Table/TableRow";
9 |
10 | import EmphasisField from "../components/Fields/EmphasisField";
11 | // import TextField from "../components/Fields/TextField";
12 | import ChipField from "../components/Fields/ChipField";
13 | import CohortField from "../components/Fields/CohortField";
14 |
15 | const data = require("../Mocks/students.json");
16 |
17 | export default {
18 | title: "Table",
19 | decorators: [withDesign]
20 | };
21 |
22 | console.log("DATA", data.students[0]);
23 | const props = {
24 | id: 1,
25 | neu_id: 40029272,
26 | name: "Maurine Rath",
27 | avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/iduuck/128.jpg",
28 | status: "graduated",
29 | gpa: 1.289,
30 | test_avg: 84,
31 | cohort: {
32 | current: "19/24",
33 | original: "19/24",
34 | moved: false
35 | }
36 | };
37 |
38 | export const Row = () => (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {/* */}
49 |
50 |
51 |
52 | );
53 |
54 | export const RowHeader = () => (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 |
69 | RowHeader.story = {
70 | parameters: {
71 | design: {
72 | type: "figma",
73 | url:
74 | "https://www.figma.com/file/QBWcVYJkF6K8o7WErRPsxd/Student-List-Mockups?node-id=31%3A412"
75 | }
76 | }
77 | };
78 |
79 | Row.story = {
80 | parameters: {
81 | design: {
82 | type: "figma",
83 | url:
84 | "https://www.figma.com/file/QBWcVYJkF6K8o7WErRPsxd/Student-List-Mockups?node-id=31%3A413"
85 | }
86 | }
87 | };
88 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Checkbox Controls/CheckboxButtonGroup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a group of checkbox buttons.
4 | * There is one checkbox created for each children.
5 | * This function also accepts an onChange function that will be executed every time
6 | * a check box is unchecked.
7 | *
8 | * Date: 04-23-2020
9 | * This function also accepts an onChange function that will be exected every time
10 | * a check box is unchecked.
11 | */
12 |
13 | // -------------------------- IMPORTS --------------------------
14 |
15 | // Function Imports
16 | import React, { cloneElement } from "react";
17 | // Component Imports
18 | import FormGroup from "@material-ui/core/FormGroup";
19 | import FormControl from "@material-ui/core/FormControl";
20 | import FormLabel from "@material-ui/core/FormLabel";
21 | import CheckboxButton from "./CheckboxButton";
22 |
23 | // -------------------------- COMPONENT --------------------------
24 |
25 | const CheckboxButtonGroup = props => {
26 | const {
27 | checkboxCheckedClass,
28 | checkboxClassName,
29 | children,
30 | className,
31 | color,
32 | error,
33 | formGroupClassName,
34 | label,
35 | onChange,
36 | showLabel,
37 | } = props;
38 |
39 | return (
40 |
41 | {showLabel && {label} }
42 |
43 | {children &&
44 | children.map((child, index) => {
45 | // the child is expected to be a Material UI - FormControlLabel
46 | // this allows for the component to handle create a checkbox group of any size
47 | return cloneElement(child, {
48 | key: index,
49 | control: (
50 |
63 | )
64 | });
65 | })}
66 |
67 |
68 | );
69 | };
70 |
71 | CheckboxButtonGroup.defaultProps = {
72 | showLabel: true
73 | };
74 |
75 | export default CheckboxButtonGroup;
76 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/BarGraph.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React, { useState } from "react";
5 |
6 | // Style Imports
7 | import { blue } from "@material-ui/core/colors";
8 | import tw, { styled, css } from "twin.macro";
9 | import "./Temp.css";
10 |
11 | // -------------------------- COMPONENT --------------------------
12 |
13 | const Bar = ({ max, min, height, threshold }) => {
14 | const heightStr = `${height.toString()}px`;
15 | // this bar covers (from threshold[0] to threshold[1])
16 | let secondHeight;
17 | const withinThreshold = min < threshold[1] && max > threshold[0];
18 | let animate;
19 |
20 | if (withinThreshold) {
21 | // the bar is within the range
22 | secondHeight = 0;
23 | animate = "";
24 | } else {
25 | secondHeight = height;
26 | animate = "bar";
27 | }
28 |
29 | const secondHeightStr = `${secondHeight.toString()}px`;
30 | const topStr = `-${height.toString()}px`;
31 | return (
32 |
42 | );
43 | };
44 |
45 | const BarGraph = ({ value, barGraphData, css, ...props }) => {
46 | // bar graph data is an array, where each element is a bar
47 |
48 | // get the max and the min from value an array of size 2
49 | let max;
50 | let min;
51 | if (value[0] > value[1]) {
52 | max = value[0];
53 | min = value[1];
54 | } else {
55 | max = value[1];
56 | min = value[0];
57 | }
58 |
59 | const barGraphHeight = 60;
60 |
61 | // get the biggest number in the array
62 | const biggestFrequency = Math.max(...barGraphData);
63 |
64 | const interval = (1 / barGraphData.length) * 4;
65 |
66 | // the biggest number bar will be the height of the bar graph
67 | // use this number to determine the height of the other bars
68 | // Ex: x's height = (x / biggest_num) * height of bar graph
69 | return (
70 |
71 | {barGraphData.map((frequency, index) => {
72 | // add height to div: height: heightRatio * height
73 | const height = (frequency / biggestFrequency) * barGraphHeight;
74 |
75 | const threshold = [index * interval, (index + 1) * interval];
76 | return ;
77 | })}
78 |
79 | );
80 | };
81 |
82 | export default BarGraph;
83 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Nav/NavItemSecondary.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React, { useCallback } from "react";
5 | import PropTypes from "prop-types";
6 | // Style Imports
7 | import tw, { styled } from "twin.macro";
8 | // Component Imports
9 | import MenuItemMaterial from "@material-ui/core/MenuItem";
10 | import ListItemIcon from "@material-ui/core/ListItemIcon";
11 | import Tooltip from "../Basic/Tooltip";
12 | import Icon from "../Basic/Icon";
13 |
14 | const activeClassname = `NavItemLink-Active${btoa(Math.random())}`;
15 |
16 | const Item = styled(({ className, ...props }) => (
17 |
18 | ))`
19 | ${tw`w-18 h-18 p-0 fontStyle-7`}
20 | ${props =>
21 | props.sidebarIsOpen
22 | ? tw`w-full h-18 p-0 fontStyle-7`
23 | : tw`w-18 h-18 p-0 fontStyle-7`}
24 |
25 | .MuiListItemIcon-root {
26 | flex-grow: ${props => (props.sidebarIsOpen ? 0 : 1)};
27 | ${tw`flex justify-center w-18`}
28 | }
29 |
30 | &.${activeClassname} {
31 | // Currently Empty
32 | }
33 | `;
34 |
35 | const IconItem = styled(ListItemIcon)`
36 | flex-grow: 1;
37 | ${tw`flex justify-center`}
38 | `;
39 |
40 | const NavItemSecondary = (
41 | { className, title, iconSrc, onClick, sidebarIsOpen, isActive, ...props },
42 | ref
43 | ) => {
44 | const handleMenuTap = useCallback(
45 | e => {
46 | onClick && onClick(e);
47 | },
48 | [onClick]
49 | );
50 |
51 | const renderNavItem = () => {
52 | return (
53 | -
60 | {iconSrc && (
61 |
62 |
68 |
69 | )}
70 | {sidebarIsOpen && title}
71 |
72 | );
73 | };
74 |
75 | if (sidebarIsOpen) {
76 | return renderNavItem();
77 | }
78 | return (
79 |
80 | {renderNavItem()}
81 |
82 | );
83 | };
84 |
85 | NavItemSecondary.propTypes = {
86 | className: PropTypes.string,
87 | iconSrc: PropTypes.object,
88 | onClick: PropTypes.func,
89 | title: PropTypes.node,
90 | staticContext: PropTypes.object,
91 | to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
92 | sidebarIsOpen: PropTypes.bool,
93 | isActive: PropTypes.bool
94 | };
95 |
96 | export default NavItemSecondary;
97 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/studentDetails/StudentDetailsContentGrid.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * This component determines the main layout of the StudentDetails Screen
4 | */
5 |
6 | import React from "react";
7 |
8 | import tw, { styled } from "twin.macro";
9 | import Paper from "@material-ui/core/Paper";
10 | import Grid from "@material-ui/core/Grid";
11 | import Card from "@material-ui/core/Card";
12 | import { withStyles } from "@material-ui/core/styles";
13 | import { useSelector } from "react-redux";
14 | import { Show, Tab, TabbedShowLayout, TabbedShowLayoutTabs } from "react-admin";
15 |
16 | import StudentCourseList from "./StudentCourseList";
17 | import StudentExamList from "./StudentExamList";
18 |
19 | const MainGrid = styled(Grid)`
20 | ${tw`pt-12 `}
21 | `;
22 |
23 | const StyledCard = styled(Card)`
24 | ${tw`paperStyle`}
25 | height: auto;
26 | `;
27 |
28 | const StyledTabLayout = styled(TabbedShowLayout)`
29 | .MuiDivider-root {
30 | display: none;
31 | }
32 | `;
33 |
34 | const StyledTabs = withStyles({
35 | root: {
36 | padding: "30px"
37 | },
38 | indicator: {
39 | display: "none"
40 | }
41 | })(TabbedShowLayoutTabs);
42 |
43 | const StyledTab = styled(Tab)`
44 | ${tw`fontStyle-6 font-medium`}
45 | text-transform: none;
46 | color: ${props => (props.selected ? "#F0F4FF" : "#4573ee")};
47 | background: ${props => (props.selected ? "#4573ee" : "#F0F4FF")};
48 | border-radius: 14px;
49 | margin-right: 16px;
50 | `;
51 |
52 | const StudentDetailsContentGrid = props => {
53 | const isOpen = true;
54 | const { record } = props;
55 |
56 | return (
57 |
58 |
59 |
60 | {/* */}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
76 | }>
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default StudentDetailsContentGrid;
91 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/RangeSlider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a slider.
4 | *
5 | * Date: 03-25-2021
6 | */
7 |
8 | // -------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React from "react";
12 | import PropTypes from "prop-types";
13 |
14 | // Component Imports
15 | import Slider from "@material-ui/core/Slider";
16 |
17 | // Style Imports
18 | import { styled } from "twin.macro";
19 |
20 | // -------------------------- COMPONENT --------------------------
21 |
22 | const Label = styled.div``;
23 |
24 | function valuetext(value) {
25 | return `${value}`;
26 | }
27 |
28 | /*
29 | TODO: Might change onCHnage to onCHnageCommited
30 | for performace reason. Thiss will trigger the
31 | onChange after the user lift the mouse press up
32 | */
33 | const RangeSlider = props => {
34 | const {
35 | onChange,
36 | setValueText,
37 | disabled,
38 | max,
39 | min,
40 | step,
41 | sliderClasses,
42 | value,
43 | setValue
44 | } = props;
45 |
46 | let rangeValue;
47 | let setRangeValue;
48 |
49 | if (value == null || setValue == null) {
50 | // Values were not given
51 | [rangeValue, setRangeValue] = React.useState([min, max]);
52 | } else {
53 | // use the variables givens
54 | rangeValue = value;
55 | setRangeValue = setValue;
56 | }
57 |
58 | const handleChange = (event, newValue) => {
59 | setRangeValue(newValue);
60 | onChange(event, newValue);
61 | };
62 |
63 | return (
64 |
81 | );
82 | };
83 |
84 | RangeSlider.propTypes = {
85 | // Typecheck for function with two arguments
86 | onChange(props, propName, componentName) {
87 | const fn = props[propName];
88 | if (
89 | !fn.prototype ||
90 | (typeof fn.prototype.constructor !== "function" && fn.prototype.constructor.length !== 2)
91 | ) {
92 | return new Error(`${propName}must be a function with 2 args`);
93 | }
94 | },
95 | setValueText: PropTypes.func,
96 | min: PropTypes.number,
97 | max: PropTypes.number,
98 | step: PropTypes.number,
99 | disabled: PropTypes.bool
100 | };
101 |
102 | RangeSlider.defaultProps = {
103 | setValueText: value => `${value}`,
104 | min: 0,
105 | max: 100,
106 | step: 0.5,
107 | disabled: false
108 | };
109 |
110 | export default RangeSlider;
111 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Fields/ScoredListField/ScoredListItem.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import tw, { styled } from "twin.macro";
3 | import PropTypes from "prop-types";
4 | import ListItem from "@material-ui/core/ListItem";
5 | import ListItemIcon from "@material-ui/core/ListItemIcon";
6 | import ListItemTextMaterial from "@material-ui/core/ListItemText";
7 | import ListItemSecondaryActionMaterial from "@material-ui/core/ListItemSecondaryAction";
8 | import ScoredListItemError from "./ScoredListItemError";
9 | import ScoredListItemSkeleton from "./ScoredListItemSkeleton";
10 | import StatusBullet from "../../Basic/StatusBullet";
11 |
12 | function statusMapping(value) {
13 | if (value >= 90) {
14 | return "success";
15 | }
16 | if (value >= 80) {
17 | return "good";
18 | }
19 | if (value >= 70) {
20 | return "warning";
21 | }
22 | if (value >= 0) {
23 | return "danger";
24 | }
25 | return "neutral";
26 | }
27 |
28 | const ListItemText = styled(ListItemTextMaterial)`
29 | & .MuiListItemText-primary {
30 | ${tw`fontStyle-4 leading-6 font-medium block truncate`}
31 | }
32 |
33 | & .MuiListItemText-secondary {
34 | ${tw`fontStyle-2 text-gray-600`}
35 | }
36 | `;
37 |
38 | const ListItemSecondaryAction = styled(ListItemSecondaryActionMaterial)`
39 | ${tw`fontStyle-4 tracking-wider font-bold text-gray-900 right-1`}
40 | `;
41 |
42 | export const ScoredItem = ({ primaryText, secondaryText, value, ...rest }) => {
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 | {value}
51 |
52 |
53 | );
54 | };
55 |
56 | ScoredItem.muiName = "ListItem";
57 |
58 | const ScoredListItem = ({
59 | primaryText,
60 | secondaryText,
61 | value,
62 | variant,
63 | loading,
64 | error,
65 | errorText,
66 | ...rest
67 | }) => {
68 | if (loading) {
69 | return ;
70 | }
71 | if (error) {
72 | return ;
73 | }
74 | return (
75 |
81 | );
82 | };
83 |
84 | ScoredListItem.propTypes = {
85 | primaryText: PropTypes.string.isRequired,
86 | secondaryText: PropTypes.string,
87 | value: PropTypes.number,
88 | variant: PropTypes.oneOf(["primary", "secondary"]).isRequired,
89 | loading: PropTypes.bool,
90 | error: PropTypes.bool,
91 | errorText: PropTypes.string
92 | };
93 |
94 | ScoredListItem.defaultProps = {
95 | variant: "primary",
96 | errorText: "Error Loading Data"
97 | };
98 | export default ScoredListItem;
99 |
--------------------------------------------------------------------------------
/pharmd-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pharmd-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.3",
7 | "@material-ui/icons": "^4.11.2",
8 | "@material-ui/lab": "^4.0.0-alpha.47",
9 | "@storybook/addon-knobs": "^5.3.17",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.4.0",
12 | "@testing-library/user-event": "^7.2.1",
13 | "axios": "^0.21.1",
14 | "clsx": "^1.1.1",
15 | "jwt-decode": "^2.2.0",
16 | "prop-types": "^15.7.2",
17 | "ra-data-json-server": "^3.1.3",
18 | "react": "^16.12.0",
19 | "react-admin": "^3.1.3",
20 | "react-dom": "^16.12.0",
21 | "react-scripts": "3.3.0",
22 | "styled-components": "^5.0.1",
23 | "twin.macro": "^1.11.0",
24 | "xlsx": "^0.15.5"
25 | },
26 | "scripts": {
27 | "start": "webpack-dev-server",
28 | "build": "webpack --mode production",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject",
31 | "storybook": "start-storybook -p 9009 -s public",
32 | "build-storybook": "build-storybook -s public"
33 | },
34 | "eslintConfig": {
35 | "extends": "react-app"
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | },
49 | "devDependencies": {
50 | "@babel/core": "^7.8.7",
51 | "@babel/plugin-proposal-class-properties": "^7.4.4",
52 | "@babel/preset-env": "^7.4.5",
53 | "@babel/preset-react": "^7.0.0",
54 | "@storybook/addon-actions": "^5.3.18",
55 | "@storybook/addon-links": "^5.3.13",
56 | "@storybook/addons": "^5.3.13",
57 | "@storybook/preset-create-react-app": "^1.5.2",
58 | "@storybook/react": "^5.3.13",
59 | "@svgr/webpack": "^5.1.0",
60 | "babel-loader": "^8.0.6",
61 | "babel-plugin-macros": "^2.8.0",
62 | "copy-webpack-plugin": "^5.0.5",
63 | "css-loader": "^2.1.1",
64 | "dotenv": "^8.2.0",
65 | "eslint": "^7.3.1",
66 | "eslint-config-airbnb": "^18.2.1",
67 | "eslint-config-airbnb-base": " ^14.0.0",
68 | "eslint-config-prettier": "^6.10.0",
69 | "eslint-plugin-import": "^2.22.1",
70 | "eslint-plugin-jsx-a11y": "^6.2.3",
71 | "eslint-plugin-prettier": "^3.1.2",
72 | "eslint-plugin-react": "^7.14.3",
73 | "eslint-plugin-react-hooks": "^1.7.0",
74 | "html-loader": "^0.5.5",
75 | "html-webpack-plugin": "^3.2.0",
76 | "prettier": "^1.19.1",
77 | "storybook-addon-designs": "^5.2.0",
78 | "storybook-addon-styled-component-theme": "^1.3.0",
79 | "style-loader": "^0.23.1",
80 | "tailwind.macro": "^1.0.0-alpha.10",
81 | "url-loader": "^3.0.0",
82 | "webpack": "^4.41.2",
83 | "webpack-cli": "^3.3.5",
84 | "webpack-dev-server": "^3.11.0"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Nav/NavItemLink.js:
--------------------------------------------------------------------------------
1 | // -------------------------- IMPORTS --------------------------
2 |
3 | // Function Imports
4 | import React, { forwardRef, useCallback } from "react";
5 | import PropTypes from "prop-types";
6 | // Style Imports
7 | import tw, { styled } from "twin.macro";
8 | // Component Imports
9 | import { NavLink } from "react-router-dom";
10 | import MenuItemMaterial from "@material-ui/core/MenuItem";
11 | import ListItemIcon from "@material-ui/core/ListItemIcon";
12 | import Tooltip from "../Basic/Tooltip";
13 | import Icon from "../Basic/Icon";
14 |
15 | const NavLinkRef = forwardRef((props, ref) => );
16 | const activeClassname = `NavItemLink-Active${btoa(Math.random())}`;
17 |
18 | // -------------------------- STYLE --------------------------
19 | const Item = styled(({ className, ...props }) => (
20 |
21 | ))`
22 | ${tw`w-24 h-24 p-0`}
23 |
24 | .MuiListItemIcon-root {
25 | flex-grow: 1;
26 | ${tw`flex justify-center`}
27 | }
28 |
29 | &.${activeClassname} {
30 | // Currently Empty
31 | }
32 | `;
33 |
34 | const IconItem = styled(ListItemIcon)`
35 | flex-grow: 1;
36 | ${tw`flex justify-center`}
37 | `;
38 |
39 | // -------------------------- COMPONENT --------------------------
40 | const NavItemLink = forwardRef(
41 | ({ className, title, iconSrc, onClick, sidebarIsOpen, isActive, ...props }, ref) => {
42 | // Navigate to Route
43 | const handleMenuTap = useCallback(
44 | e => {
45 | onClick && onClick(e);
46 | },
47 | [onClick]
48 | );
49 |
50 | const renderNavItem = () => {
51 | return (
52 | -
59 | {iconSrc && (
60 |
61 |
67 |
68 | )}
69 | {sidebarIsOpen && title}
70 |
71 | );
72 | };
73 |
74 | if (sidebarIsOpen) {
75 | return renderNavItem();
76 | }
77 | return (
78 |
79 | {renderNavItem()}
80 |
81 | );
82 | }
83 | );
84 |
85 | NavItemLink.propTypes = {
86 | className: PropTypes.string,
87 | iconSrc: PropTypes.object,
88 | onClick: PropTypes.func,
89 | title: PropTypes.node,
90 | staticContext: PropTypes.object,
91 | to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
92 | sidebarIsOpen: PropTypes.bool,
93 | isActive: PropTypes.bool
94 | };
95 |
96 | export default NavItemLink;
97 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/FileUploadService.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import * as XLSX from "xlsx";
3 | import * as BackendRoutes from "../config/backendRoutes";
4 | import {arrayToObject} from "./Utility";
5 |
6 | /**
7 | * @typedef { import('../typeDefs.js').BasicStudentAssessment} BasicStudentAssessment
8 | * @typedef { import('../typeDefs.js').FileData} FileData
9 | */
10 |
11 | class FileUploadService {
12 | static apiPrefix = "assessments/";
13 | static EMPTY_CELL_VAL = "N/A";
14 |
15 | // Enum holding the types of data to import to database
16 | static gradeInputTypes = {
17 | UNUSED: "Unused",
18 | STUDENT_NAME: "Student Name (Full)",
19 | STUDENT_LAST_NAME: "Student Last Name",
20 | STUDENT_FIRST_NAME:" Student First Name",
21 | EXAM: "Exam Grade",
22 | HOMEWORK: "Homework Grade",
23 | STUDENT_ID: "Student ID",
24 | SECTION: "Class Section",
25 | };
26 |
27 | /**
28 | * Parses array of arrays holding the contents of each spreadsheet row, to populate grade data
29 | * @param sheetData the data from the spreadsheet
30 | */
31 | static parseGradeData(sheetData) {
32 | const fields = Object.keys(sheetData[0]);
33 | }
34 |
35 | static parseInitialLoadData(sheetData) {}
36 |
37 | /**
38 | * Loads and begins parsing a spreadsheet
39 | * @param data the data representing the spreadsheet
40 | * @returns {Promise}
41 | */
42 | static async loadSpreadsheet(data) {
43 | const reader = new FileReader();
44 | let result = new Promise((resolve, reject) => {
45 | reader.onload = event => {
46 | const binary = event.target.result;
47 | const workbook = XLSX.read(binary, { type: "binary" });
48 | // Grab the first worksheet
49 | const firstSheetName = workbook.SheetNames[0];
50 | const firstSheet = workbook.Sheets[firstSheetName];
51 | // Convert sheet to an array of arrays
52 | let data = XLSX.utils.sheet_to_json(firstSheet, { blankrows: true, defval: FileUploadService.EMPTY_CELL_VAL, header: 1 });
53 | const headers = Object.values(data[0]);
54 | const subHeaders = Object.values(data[1]);
55 | if (data) {
56 | resolve({
57 | data: data.slice(2).map(arr => arrayToObject(arr, headers)),
58 | subHeaders,
59 | headers: headers
60 | });
61 | } else {
62 | reject("Couldn't load spreadsheet");
63 | }
64 | };
65 | });
66 | await reader.readAsBinaryString(data);
67 | return result;
68 | }
69 |
70 | /**
71 | * Uploads student assessment data to the DB
72 | * @param data {Array}
73 | * @returns {Promise>}
74 | */
75 | static UploadStudentAssessmentData(data) {
76 | const path = 'student-assessments/many/';
77 | return axios.post(`${BackendRoutes.BACKEND_URL}${path}`, data);
78 | }
79 | }
80 |
81 | export default FileUploadService;
82 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/profile/ProfileManage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { BooleanInput, SimpleForm, TextInput, useNotify } from "react-admin";
3 | import axios from "axios";
4 |
5 | import { BACKEND_USERS } from "../../config/backendRoutes";
6 | import ProfilePage from "./ProfilePage";
7 |
8 | function ProfileManage() {
9 | const [data, setData] = useState({
10 | firstName: "",
11 | lastName: "",
12 | email: "",
13 | password: "",
14 | isAdmin: ""
15 | });
16 | const [isAdmin, setIsAdmin] = useState(false);
17 |
18 | const notify = useNotify();
19 |
20 | useEffect(() => {
21 | const info = JSON.parse(localStorage.getItem("userInfo"));
22 | const permissions = localStorage.getItem("permissions");
23 | if (permissions === "admin") {
24 | setIsAdmin(true);
25 | }
26 | const url = `${BACKEND_USERS}${info.userID}`;
27 | axios
28 | .get(url)
29 | .then(res => {
30 | delete res.data.password;
31 | // TODO: remove this once we're getting real data
32 | setData({
33 | firstName: "Foo",
34 | lastName: "bar",
35 | email: "b@a.com",
36 | password: "pass",
37 | isAdmin: true
38 | });
39 | // setData(res.data)
40 | })
41 | .catch(err => {
42 | notify("error fetching student data");
43 | });
44 | }, []);
45 |
46 | const handleSave = values => {
47 | axios
48 | .put(BACKEND_USERS, values)
49 | .then(res => {
50 | notify("Updating user successful", "warning");
51 | })
52 | .catch(err => {
53 | notify("error updating user");
54 | });
55 | };
56 |
57 | const handleCreate = values => {
58 | axios
59 | .post(BACKEND_USERS, values)
60 | .then(res => {
61 | notify("Creating user successful", "warning");
62 | })
63 | .catch(err => {
64 | notify("error creating user");
65 | });
66 | };
67 |
68 | return (
69 |
70 | Edit Profile
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {isAdmin && (
81 | <>
82 | Create Profile
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | >
91 | )}
92 |
93 | );
94 | }
95 |
96 | export default ProfileManage;
97 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/StudentAssessmentService.js:
--------------------------------------------------------------------------------
1 | import * as BackendRoutes from "../config/backendRoutes"
2 | import axios from "axios";
3 |
4 | /**
5 | * @typedef { import('../typeDefs.js').BasicStudentAssessment} BasicStudentAssessment
6 | */
7 |
8 | class StudentAssessmentService {
9 | static apiPrefix = 'student-assessments/';
10 |
11 | // GET Methods
12 |
13 | static getAssessmentsFromStudent = studentID => {
14 | const path = this.apiPrefix + studentID;
15 | return axios.get(`${BackendRoutes.BACKEND_URL}${path}`);
16 | };
17 |
18 | // POST Methods
19 |
20 | /**
21 | * Adds a new student assessment to the backend
22 | * @param studentID {string}
23 | * @param assessment {}
24 | * @returns {Promise>}
25 | */
26 | static addNewStudentAssessment = (studentID, assessment) => {
27 | const path = this.apiPrefix + studentID;
28 | return axios.post(`${BackendRoutes.BACKEND_URL}${path}`, assessment)
29 | };
30 |
31 | /**
32 | * Given a string that contains a course term, finds it
33 | * @param courseName {string}
34 | * @returns {string}
35 | */
36 | static findCourseNameTerm = (courseName) => {
37 | const words = courseName.split(' ');
38 | let wordIndex = -1;
39 | const semester = words.find((el, index) => {
40 | switch (el) {
41 | case 'Spring':
42 | case 'Summer':
43 | case 'Fall':
44 | wordIndex = index;
45 | return true;
46 | default:
47 | return false;
48 | }
49 | });
50 | if (semester) {
51 | return semester + ' ' + words[wordIndex + 1];
52 | }
53 | return '';
54 | };
55 |
56 | /**
57 | * Makes call to backend to upload these assessments to the DB
58 | * @param assessments {Array}
59 | * @param gradeTotals Array
60 | */
61 | static addManyStudentAssessments = (assessments, gradeTotals) => {
62 | const path = this.apiPrefix + 'many/';
63 | const formattedExams = assessments.map((ass, index) => {
64 | const courseID = ass.courseName.split(' ')[0];
65 | const courseTerm = StudentAssessmentService.findCourseNameTerm(ass.courseName);
66 | const percentage = ass.percentage; // (parseInt(gradeTotals[index]) || 1);
67 | return {
68 | NUID: ass.NUID.replace(/\D/g,'') || '',
69 | courseID,
70 | courseTerm,
71 | assessmentName: ass.examName,
72 | percentage,
73 | courseName: ass.courseName,
74 | lastName: ass.studentName.split(',')[0].replace(' ', ''),
75 | firstName: ass.studentName.split(',')[1].replace(' ', '')
76 | }
77 | });
78 | const url = `${BackendRoutes.BACKEND_URL}${path}`;
79 | return axios.post(url, formattedExams)
80 | };
81 | }
82 |
83 | export default StudentAssessmentService;
84 |
--------------------------------------------------------------------------------
/pharmd-app/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/pharmd-app/src/themes/field-colors.js:
--------------------------------------------------------------------------------
1 | import { FIELD_COLOR_NAMES, FIELD_COLOR_VALUES } from "./light-theme";
2 | import { LETTER_GRADE, STUDENT, STUDENT_COURSE, STUDENT_STATUS } from "../constants/apiObjects";
3 |
4 | // get color value for the given field
5 | export const getFieldColor = ({ record, source }) =>
6 | getFieldColorValue(getFieldColorName({ record, source }));
7 |
8 | // get color value for the given field
9 | export const getFieldBackgroundColor = ({ record, source }) =>
10 | getFieldBackgroundColorValue(getFieldColorName({ record, source }));
11 |
12 | // get value of color w/given name
13 | export const getFieldColorValue = colorName => FIELD_COLOR_VALUES[colorName];
14 |
15 | // get value of background color for the color w/ given name
16 | export const getFieldBackgroundColorValue = colorName =>
17 | getFieldColorValue(colorName).replace("1)", "0.3)");
18 |
19 | // get color name for given field
20 | export const getFieldColorName = ({ record, source }) => {
21 | switch (source) {
22 | case STUDENT.GPA:
23 | return getGPAColor({ record });
24 | case STUDENT_COURSE.LETTER_GRADE:
25 | return getLetterGradeColor({ record });
26 | case STUDENT_COURSE.PERCENTAGE:
27 | return getPercentageColor({ record });
28 | case STUDENT.STATUS:
29 | return getStudentStatusColor({ record });
30 | default:
31 | return FIELD_COLOR_NAMES.DEFAULT;
32 | }
33 | };
34 |
35 | const getLetterGradeColor = ({ record }) => {
36 | switch (record[STUDENT_COURSE.LETTER_GRADE]) {
37 | case LETTER_GRADE.A:
38 | case LETTER_GRADE.A_MINUS:
39 | case LETTER_GRADE.B_PLUS:
40 | case LETTER_GRADE.B:
41 | return FIELD_COLOR_NAMES.GREEN;
42 | case LETTER_GRADE.B_MINUS:
43 | case LETTER_GRADE.C_PLUS:
44 | case LETTER_GRADE.C:
45 | case LETTER_GRADE.C_MINUS:
46 | return FIELD_COLOR_NAMES.ORANGE;
47 | case LETTER_GRADE.D_PLUS:
48 | case LETTER_GRADE.D:
49 | case LETTER_GRADE.D_MINUS:
50 | case LETTER_GRADE.F:
51 | return FIELD_COLOR_NAMES.RED;
52 | default:
53 | return FIELD_COLOR_NAMES.DEFAULT;
54 | }
55 | };
56 |
57 | const getGPAColor = ({ record }) => {
58 | const gpa = record[STUDENT.GPA];
59 | if (gpa >= 3.7) {
60 | return FIELD_COLOR_NAMES.GREEN;
61 | }
62 | if (gpa >= 1.7) {
63 | return FIELD_COLOR_NAMES.ORANGE;
64 | }
65 | return FIELD_COLOR_NAMES.RED;
66 | };
67 |
68 | const getPercentageColor = ({ record }) => {
69 | const percentage = record[STUDENT_COURSE.PERCENTAGE];
70 | if (percentage >= 83) {
71 | return FIELD_COLOR_NAMES.GREEN;
72 | }
73 | if (percentage >= 70) {
74 | return FIELD_COLOR_NAMES.ORANGE;
75 | }
76 | return FIELD_COLOR_NAMES.RED;
77 | };
78 |
79 | const getStudentStatusColor = ({ record }) => {
80 | switch (record[STUDENT.STATUS]) {
81 | case STUDENT_STATUS.COOP:
82 | return FIELD_COLOR_NAMES.PRIMARY;
83 | case STUDENT_STATUS.ENROLLED:
84 | return FIELD_COLOR_NAMES.GREEN;
85 | case STUDENT_STATUS.DROP_BACK:
86 | return FIELD_COLOR_NAMES.RED;
87 | case STUDENT_STATUS.LEAVE:
88 | return FIELD_COLOR_NAMES.ORANGE;
89 | case STUDENT_STATUS.GRADUATED:
90 | return FIELD_COLOR_NAMES.TERTIARY;
91 | default:
92 | return FIELD_COLOR_NAMES.DEFAULT;
93 | }
94 | };
95 |
--------------------------------------------------------------------------------
/pharmd-app/src/services/Data/GetHooks.js:
--------------------------------------------------------------------------------
1 | // React Admin hooks that will makes calls to the API to GET content
2 | import { stringify } from "query-string";
3 | import { fetchUtils } from "react-admin";
4 |
5 | /**
6 | * Returns object with hooks for getting resources
7 | * @param {Function} httpClient The function to make an HTTP request
8 | * @param {string} BACKEND_URL the base URL to make a request to
9 | * @return {{getList: (function(*, *=): *), getMany: (function(*, *): *), getManyReference: (function(*, *): *), getOne: (function(*, *): *)}}
10 | */
11 | export default (httpClient, BACKEND_URL) => ({
12 | getList: (resource, params) => {
13 | const { page, perPage } = params.pagination;
14 | const { field, order } = params.sort;
15 | const query = {
16 | ...fetchUtils.flattenObject(params.filter),
17 | _sort: field,
18 | _order: order,
19 | _start: (page - 1) * perPage,
20 | _end: page * perPage
21 | };
22 | const url = `${BACKEND_URL}/${resource}?${stringify(query)}`;
23 | return httpClient(url).then(({ headers, json, status }) => {
24 | if (!headers.has("x-total-count")) {
25 | return Promise.reject(
26 | "The X-Total-Count header is missing in the HTTP Response. The jsonServer Data Provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?"
27 | );
28 | }
29 | return {
30 | data: json,
31 | total: parseInt(
32 | headers
33 | .get("x-total-count")
34 | .split("/")
35 | .pop(),
36 | 10
37 | )
38 | };
39 | });
40 | },
41 |
42 | getOne: (resource, params) =>
43 | httpClient(`${BACKEND_URL}/${resource}/${params.id}`).then(({ json }) => ({
44 | data: json
45 | })),
46 |
47 | getMany: (resource, params) => {
48 | const query = {
49 | id: params.ids
50 | };
51 | const url = `${BACKEND_URL}/${resource}?${stringify(query)}`;
52 | return httpClient(url).then(({ json }) => ({ data: json }));
53 | },
54 |
55 | getManyReference: (resource, params) => {
56 | const { page, perPage } = params.pagination;
57 | const { field, order } = params.sort;
58 | const query = {
59 | ...fetchUtils.flattenObject(params.filter),
60 | [params.target]: params.id,
61 | _sort: field,
62 | _order: order,
63 | _start: (page - 1) * perPage,
64 | _end: page * perPage
65 | };
66 | const url = `${BACKEND_URL}/${resource}?${stringify(query)}`;
67 |
68 | return httpClient(url).then(({ headers, json }) => {
69 | if (!headers.has("x-total-count")) {
70 | return Promise.reject(
71 | "The X-Total-Count header is missing in the HTTP Response. The jsonServer Data Provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?"
72 | );
73 | }
74 | return {
75 | data: json,
76 | total: parseInt(
77 | headers
78 | .get("x-total-count")
79 | .split("/")
80 | .pop(),
81 | 10
82 | )
83 | };
84 | });
85 | }
86 | });
87 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Inputs/GpaSliderInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a slider to filter Student data based on GPA.
4 | *
5 | * Date: 03-18-2021
6 | */
7 |
8 | //-------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React from "react";
12 | import { useInput } from "react-admin";
13 |
14 | // Component Imports
15 | import RangeSlider from "../Basic/RangeSlider";
16 | import { withStyles } from "@material-ui/core/styles";
17 |
18 | // Style Imports
19 | import { blue } from "@material-ui/core/colors";
20 | import BarGraph from "../Basic/BarGraph";
21 | import Slider from "@material-ui/core/Slider";
22 |
23 | // -------------------------- STYLE --------------------------
24 |
25 | const styles = {
26 | // the line behind the range showing the full width of the range
27 | rail: {
28 | height: "5px"
29 | },
30 |
31 | // rangSlider styling
32 | rangeSlider: {
33 | margin: "2rem 0 0 0",
34 | width: 300,
35 | "& h4": {
36 | margin: "0 0 1.33em 0",
37 | color: "black",
38 | fontWeight: "650",
39 | fontSize: "1.1rem",
40 | fontFamily:
41 | "Montserrat-SemiBold, Montserrat-Bold, Inter-Medium, Inter-SemiBold, Inter-Regular, Inter-Bold, sans-serif",
42 | "& span": {
43 | color: blue[700],
44 | fontSize: "1rem"
45 | }
46 | }
47 | },
48 |
49 | // change the styling of the handles (round clickable object at either end of the slider)
50 | thumb: {
51 | height: "15px",
52 | width: "15px"
53 | },
54 |
55 | // the line that dynamically shrinks or grows based on the user's interaction with this component
56 | track: {
57 | height: "5px"
58 | }
59 | };
60 |
61 | //-------------------------- COMPONENT --------------------------
62 |
63 | function valuetext(value) {
64 | return `${value}`;
65 | }
66 |
67 | const GpaSliderInput = ({ classes, setFilter, value, setValue, ...props }) => {
68 | const {
69 | meta: { error }
70 | } = useInput(props);
71 |
72 | // Adds a gpa filter to student data
73 | const searchGpaRange = (event, newValue) => {
74 | if (newValue) {
75 | const gpaMin = newValue[0];
76 | const gpaMax = newValue[1];
77 | setFilter("gpa", [gpaMin, gpaMax]);
78 | }
79 | };
80 |
81 | // compute height difference between bar graph and slider
82 | // slider to top - (bar graph to top + height of bar graph)
83 | const top = 14;
84 | const topStr = top.toString() + "px";
85 | const css = { top: topStr };
86 |
87 | // TODO: Remove temporary data and add real data from back end
88 | const frequencies = [1, 2, 2.25, 2.5, 1.75, 2.75, 2.25, 2, 2.5, 1.5];
89 |
90 | return (
91 |
92 |
93 | GPA Range {`(${value[0]} - ${value[1]})`}
94 |
95 |
100 |
111 |
112 | );
113 | };
114 |
115 | export default withStyles(styles)(GpaSliderInput);
116 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Breadcrumb.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * Component creates breadcrumbs linking to different routes
4 | */
5 |
6 | // -------------------------- IMPORTS --------------------------
7 |
8 | // Function Imports
9 | import React from "react";
10 | import { Route } from "react-router";
11 | import PropTypes from "prop-types";
12 | // Component Imports
13 | import { Link as RouterLink } from "react-router-dom";
14 | import MuiLink from "@material-ui/core/Link";
15 | import MuiBreadcrumbs from "@material-ui/core/Breadcrumbs";
16 | // Style Imports
17 | import tw, { styled } from "twin.macro";
18 | import { MAP_BREADCRUMB_NAME } from "../../constants/mappers";
19 | import {
20 | HOME_TITLE,
21 | STUDENT_DETAILS_TITLE,
22 | STUDENT_QUICKVIEW_TITLE
23 | } from "../../constants/text";
24 | import { HOME_MAIN, STUDENTS_MAIN } from "../../constants/routes";
25 |
26 | // -------------------------- STYLE --------------------------
27 |
28 | const Link = styled(MuiLink)`
29 | ${tw`fontStyle-3 m-0 text-gray-600`}
30 | `;
31 |
32 | const CurrentLink = styled.p`
33 | ${tw`fontStyle-3 m-0 text-gray-500`}
34 | `;
35 |
36 | // -------------------------- FUNCTIONS --------------------------\
37 |
38 | const getName = (to, value) => {
39 | if (!isNaN(value) && to.includes(`${STUDENTS_MAIN}/${value}`)) {
40 | return STUDENT_QUICKVIEW_TITLE;
41 | }
42 | if (to.includes(`show`)) {
43 | return STUDENT_QUICKVIEW_TITLE;
44 | }
45 | if (to.includes(`details`)) {
46 | return STUDENT_DETAILS_TITLE;
47 | }
48 |
49 | // Check if route predefined
50 | const val = MAP_BREADCRUMB_NAME[to];
51 | return val || value;
52 | };
53 |
54 | export const BreadcrumbLink = props => ;
55 |
56 | // location Param added just for storybook testing
57 | function RouterBreadcrumb(locTest) {
58 | return (
59 |
60 | {({ location }) => {
61 | // For Testing
62 | const loc = locTest.locTest ? locTest.locTest : location;
63 | const pathnames = loc.pathname.split("/").filter(x => x);
64 |
65 | const generateCrumbs = () => {
66 | const crumbs = [];
67 | pathnames.forEach((value, index) => {
68 | const isLast = index === pathnames.length - 1;
69 | // Route Location Link
70 | const to = `/${pathnames.slice(0, index + 1).join("/")}`;
71 |
72 | // Filters used to remove certain routes
73 | // if (!isNaN(value)) { // Remove Number IDs Incase if quickview is not included
74 | if (value === "show") {
75 | return;
76 | }
77 |
78 | if (isLast) {
79 | // Does not have a link
80 | crumbs.push({getName(to, value)} );
81 | } else {
82 | // Linkable
83 | crumbs.push(
84 |
85 | {getName(to, value)}
86 |
87 | );
88 | }
89 | });
90 | return crumbs;
91 | };
92 | return (
93 |
94 |
95 | {HOME_TITLE}
96 |
97 | {generateCrumbs()}
98 |
99 | );
100 | }}
101 |
102 | );
103 | }
104 |
105 | RouterBreadcrumb.defaultProps = {};
106 |
107 | RouterBreadcrumb.propTypes = {
108 | locTest: PropTypes.string
109 | };
110 |
111 | export default RouterBreadcrumb;
112 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Basic/Autocomplete.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a multi select drop down / text input to filter options in the drop down.
4 | *
5 | * Date: 03-25-2021
6 | */
7 |
8 | // -------------------------- IMPORTS --------------------------
9 |
10 | // Function Imports
11 | import React, { useState } from "react";
12 |
13 | // Component Imports
14 | import AutocompleteMaterial from "@material-ui/lab/Autocomplete";
15 | import TextField from "@material-ui/core/TextField";
16 | import FilterChip from "./FilterChip";
17 |
18 | // -------------------------- COMPONENT --------------------------
19 |
20 | const Autocomplete = props => {
21 | const {
22 | classes,
23 | inputClassName,
24 | inputValue,
25 | isOptionSelected,
26 | label,
27 | onChange,
28 | options,
29 | placeholder,
30 | popupIcon,
31 | setInputValue,
32 | setValue,
33 | tagClassName,
34 | value
35 | } = props;
36 |
37 | // the state to keep track of text that is typed into the input text field
38 | let autocompleteInputValue;
39 | let setAutocompleteInputValue;
40 |
41 | if (inputValue == null || setInputValue == null) {
42 | // Values were not given
43 | [autocompleteInputValue, setAutocompleteInputValue] = useState("");
44 | } else {
45 | // use the variables givens
46 | autocompleteInputValue = inputValue;
47 | setAutocompleteInputValue = setInputValue;
48 | }
49 |
50 | // the state the keep track of the options that have been selected (this is an array object)
51 | let autocompleteValue;
52 | let setAutocompleteValue;
53 |
54 | if (value == null || setValue == null) {
55 | // Values were not given
56 | [autocompleteValue, setAutocompleteValue] = useState([]);
57 | } else {
58 | // use the variables givens
59 | autocompleteValue = value;
60 | setAutocompleteValue = setValue;
61 | }
62 |
63 | // updates the list of tags that have been added
64 | const onChangeFunc = (event, newValue) => {
65 | setAutocompleteValue(newValue);
66 | onChange(event, newValue);
67 | };
68 |
69 | // display placeholder only if there are no lists added or if there is no text input
70 | const placeholderOutput = () => {
71 | if (autocompleteValue != null && autocompleteValue.length > 0) {
72 | return undefined;
73 | }
74 | return placeholder;
75 | };
76 |
77 | return (
78 | {
83 | setAutocompleteInputValue(newInputValue);
84 | }}
85 | classes={{
86 | root: classes.root,
87 | endAdornment: classes.endAdornment,
88 | listbox: classes.listbox,
89 | noOptions: classes.noOptions,
90 | option: classes.option,
91 | paper: classes.paper,
92 | popper: classes.popper
93 | }}
94 | disableCloseOnSelect
95 | getOptionLabel={option => option.label}
96 | getOptionSelected={isOptionSelected}
97 | multiple
98 | options={options}
99 | popupIcon={popupIcon}
100 | renderInput={params => (
101 |
110 | )}
111 | renderTags={(tagValue, getTagProps) => {
112 | return tagValue.map((option, index) => (
113 |
114 | ));
115 | }}
116 | />
117 | );
118 | };
119 |
120 | export default Autocomplete;
121 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/upload/components/SpreadsheetUploader.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import UploadFileChooser from "./UploadFileChooser";
3 | import UploadDataFieldChooser from "./UploadDataFieldChooser";
4 | import StudentAssessmentService from '../../../services/StudentAssessmentService';
5 | import Button from '../../../components/Form/Button';
6 |
7 | class SpreadsheetUploader extends Component {
8 |
9 | DEFAULT_STATE = {
10 | uploadedDataHeaders: null,
11 | uploadedDataSubheaders: null,
12 | uploadedData: null,
13 | uploadComplete: null
14 | };
15 |
16 | constructor(props) {
17 | super(props);
18 | this.state = this.DEFAULT_STATE;
19 |
20 | this.uploadFile = this.uploadFile.bind(this);
21 | this.removeFile = this.removeFile.bind(this);
22 | this.confirmData = this.confirmData.bind(this);
23 | }
24 |
25 | /**
26 | * Updates the state with data about the uploaded file
27 | * @param {FileData}fileData
28 | */
29 | uploadFile(fileData) {
30 | if (fileData.data.length > 0) {
31 | this.setState({
32 | uploadedDataHeaders: fileData.headers,
33 | uploadedDataSubheaders: fileData.subHeaders,
34 | uploadedData: fileData.data,
35 | uploadComplete: null
36 | });
37 | }
38 | };
39 |
40 | removeFile() {
41 | this.setState(this.DEFAULT_STATE)
42 | }
43 |
44 | /**
45 | * Tries to upload the given data to the backend
46 | * @param data {{
47 | * exams: Array
48 | * }}
49 | */
50 | confirmData(data) {
51 | const promises = [];
52 | promises.push(StudentAssessmentService.addManyStudentAssessments(data.exams, []));
53 | Promise.all(promises)
54 | .then(res => {
55 | this.setState({
56 | ...this.DEFAULT_STATE,
57 | uploadComplete: 'Upload complete!'
58 | });
59 | })
60 | .catch(err => {
61 | this.setState({
62 | ...this.DEFAULT_STATE,
63 | uploadComplete: 'Upload failed--please try again'
64 | });
65 | })
66 | }
67 |
68 | /**
69 | * Displays a dialog with help info about the file uploader
70 | */
71 | showHelp() {
72 | const helpInfo = `
73 | This tool is used to upload spreadsheet exports from Canvas into the PharmD database
74 |
75 | To use, choose your file, then match the appropriate columns with the value type it holds.
76 |
77 | Value Types (* = required):
78 | - * Student Name (Full): The student's full name (last, first)
79 | - * Student Last Name: The student's surname
80 | - * Student First Name: The student's given name
81 | - * Exam Grade: A grade for an exam
82 | - Homework grade: Self-explanatory
83 | - * Student ID: The student's NUID
84 | - *Class Section: The course these grades correspond to
85 | `;
86 | // TODO: replace with modal dialog
87 | alert(helpInfo)
88 |
89 | }
90 |
91 | render() {
92 | return (
93 |
94 | Help
95 | {!this.state.uploadedData &&
96 | }
97 | {this.state.uploadedData && Remove File }
98 | {this.state.uploadedData && }
103 | {this.state.uploadComplete && this.state.uploadComplete}
104 |
105 | );
106 | }
107 | }
108 |
109 | export default SpreadsheetUploader;
110 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Inputs/StatusCheckboxInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a group of status filter checkbox buttons.
4 | * When you click on an status checkbox it will filter the table for data with the
5 | * selected status.
6 | *
7 | * Date: 02-18-2021
8 | */
9 |
10 | // -------------------------- IMPORTS --------------------------
11 |
12 | // Function Imports
13 | import React from "react";
14 | import { useInput } from "react-admin";
15 |
16 | // Component Imports
17 | import FormControlLabel from "@material-ui/core/FormControlLabel";
18 | import CheckboxButtonGroup from "../Basic/Checkbox Controls/CheckboxButtonGroup";
19 |
20 | // Style Imports
21 | import { withStyles } from "@material-ui/core/styles";
22 | import { blue } from "@material-ui/core/colors";
23 | import { STUDENT_STATUS } from "../../constants/apiObjects";
24 | import CheckboxFilterButtonGroup from "../Basic/Checkbox Controls/CheckboxFilterButtonGroup";
25 |
26 | // -------------------------- STYLE --------------------------
27 | // Use styles to make the label change when the checkbox is checked
28 | const styles = {
29 | checked: {
30 | // when the checkbox is checked change the fontweight of the label
31 | "&, & + $label": {
32 | fontWeight: 600
33 | }
34 | },
35 |
36 | // checkbox styling
37 | checkboxButton: {
38 | color: blue[700]
39 | },
40 |
41 | // follow the uniformity: make the label 16px away from the filter
42 | formGroup: {
43 | paddingTop: "7px"
44 | }
45 | };
46 |
47 | // -------------------------- COMPONENT --------------------------
48 |
49 | const StatusCheckboxInput = ({
50 | checkedBoxes,
51 | className,
52 | classes,
53 | color,
54 | label,
55 | setCheckedBoxes,
56 | setFilter,
57 | ...props
58 | }) => {
59 | const {
60 | meta: { error }
61 | } = useInput(props);
62 |
63 | let statusCheckedBoxes;
64 | let setStatusCheckedBoxes;
65 |
66 | // Contains a list of tuples: (String: value, String: label)
67 | const formControlLabels = [
68 | [STUDENT_STATUS.ENROLLED, "Enrolled"],
69 | [STUDENT_STATUS.COOP, "Co-op"],
70 | [STUDENT_STATUS.GRADUATED, "Graduated"],
71 | [STUDENT_STATUS.LEAVE, "Leave"],
72 | [STUDENT_STATUS.DROP_BACK, "Drop Back"]
73 | ];
74 |
75 | if (checkedBoxes == null || setCheckedBoxes == null) {
76 | // Values were not given
77 | [statusCheckedBoxes, setStatusCheckedBoxes] = React.useState([min, max]);
78 | } else {
79 | // use the variables givens
80 | statusCheckedBoxes = checkedBoxes;
81 | setStatusCheckedBoxes = setCheckedBoxes;
82 | }
83 |
84 | // Return a function that accepts an event
85 | const createOnClick = item => {
86 | return e => {
87 | const index = statusCheckedBoxes.indexOf(item);
88 | const newCheckedBoxes = [...statusCheckedBoxes];
89 | if (index < 0) {
90 | // item is not in the list
91 | newCheckedBoxes.push(item);
92 | } else {
93 | // item is already in the list - remove it
94 | newCheckedBoxes.splice(index, 1);
95 | }
96 |
97 | // after getting the updated list add / remove filters
98 | if (newCheckedBoxes.length === 5) {
99 | // all of the checkboxes are checked
100 | setFilter("status", []);
101 | } else {
102 | setFilter("status", newCheckedBoxes);
103 | }
104 |
105 | setStatusCheckedBoxes(newCheckedBoxes);
106 | };
107 | };
108 |
109 | return (
110 |
119 | {formControlLabels.map(([formControlValue, formControlLabel]) => {
120 | return (
121 | = 0}
123 | value={formControlValue}
124 | label={formControlLabel}
125 | onclick={createOnClick(formControlValue)}
126 | classes={{ label: classes.label }}
127 | />
128 | );
129 | })}
130 |
131 | );
132 | };
133 |
134 | export default withStyles(styles)(StatusCheckboxInput);
135 |
--------------------------------------------------------------------------------
/pharmd-app/src/screens/student/StudentDisplayFilters.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a list of chips to display which student filter is in use.
4 | * These chips have a close icon that when clicked will remove the respective filter and reset the respective filter
5 | * component.
6 | *
7 | * Format of filters:
8 | * cohort {current: [] }
9 | * gpa_gte: float
10 | * gpa_lte: float
11 | * international: bool
12 | * status: []
13 | * Date: 03-25-2021
14 | */
15 |
16 | // -------------------------- IMPORTS --------------------------
17 | // Function Imports
18 | import React from "react";
19 |
20 | // Component Imports
21 | import FilterChip from "../../components/Basic/FilterChip";
22 |
23 | // Style Imports
24 | import { withStyles } from "@material-ui/core/styles";
25 | import tw from "twin.macro";
26 |
27 | // -------------------------- STYLE --------------------------
28 |
29 | const Chip = withStyles({
30 | root: {
31 | backgroundColor: "#2B2B90",
32 | fontSize: "1rem",
33 | marginRight: ".3rem",
34 | marginBottom: ".3rem",
35 | // when clicked or hovered on nothing changes
36 | "&:hover, &:focus, &:active": {
37 | backgroundColor: "#2B2B90"
38 | }
39 | }
40 | })(FilterChip);
41 |
42 | // -------------------------- COMPONENT --------------------------
43 |
44 | const StudentDisplayFilters = ({
45 | deleteFilter,
46 | filterValues,
47 | setAutocompleteInputValue,
48 | setAutocompleteValue,
49 | setFilter,
50 | setOriginCheckedLabels,
51 | setRangeValue,
52 | setStatusCheckedLabels,
53 | ...props
54 | }) => {
55 | let numStatusFilters = 0;
56 | let hasGPAFilters = false;
57 | let numCohortFilters = 0;
58 | let internationalFilter = null;
59 |
60 | if ("cohort" in filterValues) {
61 | numCohortFilters = filterValues.cohort.current.length;
62 | }
63 |
64 | if ("gpa" in filterValues) {
65 | hasGPAFilters = filterValues.gpa[0] > 0 || filterValues.gpa[1] < 4;
66 | }
67 |
68 | if ("hasVisa" in filterValues) {
69 | internationalFilter = filterValues.hasVisa;
70 | }
71 |
72 | if ("status" in filterValues) {
73 | numStatusFilters = filterValues.status.length;
74 | }
75 |
76 | // reset filter and filter component functions
77 |
78 | const resetStatusValues = () => {
79 | deleteFilter("status");
80 | // this state variable is linked to the status filter component
81 | setStatusCheckedLabels([]);
82 | };
83 |
84 | const resetGPAValues = () => {
85 | setFilter("gpa", [0, 4]);
86 | // this state variable is linked to the GPA filter component
87 | setRangeValue([0, 4]);
88 | };
89 |
90 | const resetCohortValue = () => {
91 | setFilter("cohort[current]", []);
92 | // these state variable are linked to the cohort filter component
93 | setAutocompleteInputValue("");
94 | setAutocompleteValue([]);
95 | };
96 |
97 | const resetInternationalValues = () => {
98 | // this state variable is linked to the international filter component
99 | deleteFilter("hasVisa");
100 | setOriginCheckedLabels([]);
101 | };
102 |
103 | // Create International filter chip
104 | // Displays which filter is on: Domestic or International
105 | const displayInternationalFilter = () => {
106 | if (internationalFilter != null) {
107 | let label = "";
108 | if (internationalFilter) {
109 | // filters for international
110 | label = "International";
111 | } else {
112 | // filters for domestic
113 | label = "Domestic";
114 | }
115 |
116 | return (
117 | {
120 | resetInternationalValues();
121 | }}
122 | />
123 | );
124 | }
125 | };
126 |
127 | return (
128 |
129 | {numStatusFilters > 0 && (
130 | {
133 | resetStatusValues();
134 | }}
135 | />
136 | )}
137 | {hasGPAFilters && (
138 | {
141 | resetGPAValues();
142 | }}
143 | />
144 | )}
145 | {numCohortFilters > 0 && (
146 | {
149 | resetCohortValue();
150 | }}
151 | />
152 | )}
153 | {internationalFilter != null && displayInternationalFilter()}
154 |
155 | );
156 | };
157 |
158 | export default StudentDisplayFilters;
159 |
--------------------------------------------------------------------------------
/pharmd-app/src/components/Inputs/CohortMultiSelectInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Description:
3 | * This Component creates a multi select drop down / text input to filter Student data based on GPA.
4 | * This component also allows the user to type in the option they are looking for to filter the options.
5 | *
6 | * Date: 03-18-2021
7 | */
8 |
9 | // -------------------------- IMPORTS --------------------------
10 |
11 | // Function Imports
12 | import React from "react";
13 | import { useInput } from "react-admin";
14 |
15 | // Component Imports
16 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
17 |
18 | // Style Imports
19 | import { blue } from "@material-ui/core/colors";
20 | import { withStyles } from "@material-ui/core/styles";
21 | import Autocomplete from "../Basic/Autocomplete";
22 | import tw from "twin.macro";
23 |
24 | // -------------------------- STYLE --------------------------
25 |
26 | const styles = {
27 | // style the general formControl Styling
28 | root: {
29 | width: "100%",
30 | // style the default border color and size
31 | "& fieldset": {
32 | borderRadius: ".6rem",
33 | border: ".2rem solid #F0F4FF"
34 | },
35 | // style the border color and size when user focuses (clicks on) the filter component
36 | "&.Mui-focused fieldset": {
37 | border: ".2rem solid " + blue[700] + " !important"
38 | },
39 | // style the border color and size when user hovers over the filter component
40 | "&:hover fieldset": {
41 | border: ".2rem solid " + blue[700] + " !important"
42 | }
43 | },
44 |
45 | // style the container element of toggle popper icon and remove all options icon
46 | endAdornment: {
47 | top: "9px"
48 | },
49 |
50 | // style the container element of options
51 | listbox: {
52 | border: ".2rem solid " + blue[700],
53 | borderRadius: ".6rem",
54 | marginTop: ".5rem",
55 | padding: 0
56 | },
57 |
58 | // style the container element of listbox (remove the shadow)
59 | paper: {
60 | boxShadow: "unset"
61 | },
62 |
63 | // styling for each option that a user can select within pop up
64 | option: {
65 | // styling when an option has been selected
66 | "&[aria-selected=true]": {
67 | backgroundColor: "#F0F4FF"
68 | },
69 | // styling when an option is hovered on
70 | "&:hover": {
71 | backgroundColor: "#F0F4FF"
72 | }
73 | },
74 |
75 | // styling for the no options div that shows up when there are no options that match
76 | // the user's input
77 | noOptions: {
78 | border: ".2rem solid " + blue[700],
79 | borderRadius: ".6rem",
80 | marginTop: ".5rem",
81 |
82 | // match the default padding on the options
83 | paddingTop: "6px",
84 | paddingLeft: "16px",
85 | paddingRight: "16px",
86 | paddingBottom: "6px"
87 | },
88 |
89 | // style the tags (pills) or options that have been selected in the input field
90 | tag: {
91 | backgroundColor: "#4573EE",
92 | marginBottom: ".3rem",
93 | marginRight: ".3rem"
94 | }
95 | };
96 |
97 | // -------------------------- COMPONENT --------------------------
98 |
99 | const CohortMultipleSelect = ({ classes, className, inputValue, label, setFilter, setInputValue, setValue, value, ...props }) => {
100 | const {
101 | meta: { error }
102 | } = useInput(props);
103 |
104 | // setFilter (function) requires a list of values. Ex: ["15/20"]
105 | // Autocomplete will get an array of dict. Ex: [{label: "Cohort 22", value: "17/22"}]
106 | const cohortMultiSelect = (event, array) => {
107 | const values = [];
108 | array.forEach(dict => {
109 | values.push(dict.value);
110 | });
111 | setFilter("cohort[current]", values);
112 | };
113 |
114 | // determines if an option (dictionary) has been selected based on its value
115 | const isCohortSelected = (option, value) => {
116 | // checks if these two dictionaries have a "value" key
117 | if (!("value" in option || "value" in value)) {
118 | return false;
119 | }
120 |
121 | // checks if these two dictionaries have the same value (no two options should have the
122 | // same value
123 | return option.value === value.value;
124 | };
125 |
126 | const cohortList = [
127 | { label: "Cohort 20", value: "15/20" },
128 | { label: "Cohort 22", value: "17/22" },
129 | { label: "Cohort 23", value: "18/23" },
130 | { label: "Cohort 24", value: "19/24" },
131 | { label: "Cohort 25", value: "20/25" },
132 | { label: "Cohort 26", value: "20/26" }
133 | ];
134 |
135 | return (
136 |
137 |
{label}
138 |
}
148 | setInputValue={setInputValue}
149 | setValue={setValue}
150 | value={value}
151 | />
152 |
153 | );
154 | };
155 |
156 | export default withStyles(styles)(CohortMultipleSelect);
157 |
--------------------------------------------------------------------------------