diff --git a/pharmd-app/package.json b/pharmd-app/package.json
index db5e4dd7..75d1f1bb 100644
--- a/pharmd-app/package.json
+++ b/pharmd-app/package.json
@@ -55,7 +55,7 @@
"@storybook/addon-links": "^5.3.13",
"@storybook/addons": "^5.3.13",
"@storybook/preset-create-react-app": "^1.5.2",
- "@storybook/react": "^5.3.13",
+ "@storybook/react": "^5.3.21",
"@svgr/webpack": "^5.1.0",
"babel-loader": "^8.0.6",
"babel-plugin-macros": "^2.8.0",
diff --git a/pharmd-app/src/App.js b/pharmd-app/src/App.js
index 20da452b..853a9b0d 100644
--- a/pharmd-app/src/App.js
+++ b/pharmd-app/src/App.js
@@ -11,6 +11,7 @@ import { AuthProvider, DataProvider } from "./services";
import createLigthTheme from "./themes/light-theme";
import DashboardLayout from "./components/Layout/DashboardLayout";
import customRoutes from "./config/customRoutes";
+import customReducers from './redux/reducers';
const App = () => {
return (
@@ -23,6 +24,7 @@ const App = () => {
authProvider={AuthProvider}
dashboard={Dashboard}
theme={theme}
+ customReducers={customReducers}
customRoutes={customRoutes}
>
diff --git a/pharmd-app/src/components/Basic/BasicTag.js b/pharmd-app/src/components/Basic/BasicTag.js
new file mode 100644
index 00000000..1f376b19
--- /dev/null
+++ b/pharmd-app/src/components/Basic/BasicTag.js
@@ -0,0 +1,27 @@
+import React from "react";
+import tw, { styled } from "twin.macro";
+import Chip from "@material-ui/core/Chip";
+
+// ${props => props.theme.typography.size4}
+const Pill = styled(Chip)`
+
+ ${tw`rounded-lg capitalize w-16 fontStyle-4 text-xs font-bold tracking-wider`}
+ margin: 3px;
+ color: ${props => props.color};
+ background-color: #b5c7f8;
+`;
+
+const Field = styled.a`
+ ${tw`p-0`}
+`;
+
+const BasicTag = ({ text, color }) => {
+ const tagColor = color || '#f2994a'
+ return (
+
+
+
+ );
+};
+
+export default BasicTag;
\ No newline at end of file
diff --git a/pharmd-app/src/components/Basic/NoteIcon.js b/pharmd-app/src/components/Basic/NoteIcon.js
index 321d12cf..602d7cdb 100644
--- a/pharmd-app/src/components/Basic/NoteIcon.js
+++ b/pharmd-app/src/components/Basic/NoteIcon.js
@@ -16,6 +16,12 @@ const SVG = styled(SvgIcon)`
color: black;
`}
+ ${props =>
+ props.color === "red" &&
+ css`
+ color: red;
+ `}
+
${props =>
props.primary &&
css`
diff --git a/pharmd-app/src/components/Fields/NoteBoxField.js b/pharmd-app/src/components/Fields/NoteBoxField.js
new file mode 100644
index 00000000..c8457dae
--- /dev/null
+++ b/pharmd-app/src/components/Fields/NoteBoxField.js
@@ -0,0 +1,78 @@
+import React from 'react'
+import tw, { styled } from "twin.macro";
+import MuiPaper from "@material-ui/core/Paper";
+import {NOTE} from "../../constants/apiObjects";
+import CardHeader from "@material-ui/core/CardHeader";
+import CardContent from "@material-ui/core/CardContent";
+import BasicTag from "../Basic/BasicTag";
+import {DeleteButton} from 'react-admin';
+
+const CardRoot = styled.div`
+ flex-grow: 1;
+ margin: 15px;
+`
+
+const Paper = styled(MuiPaper)`
+ ${tw`rounded-xl h-64 w-56 shadow-cardLight`}
+ padding: 7px;
+ margin: auto;
+
+`;
+
+const Tags = styled.div`
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: space-around;
+`
+
+const MAX_TITLE_LENGTH = 25;
+const MAX_BODY_LENGTH = 30;
+
+const NoteBoxField = ({ record, studentId, onDelete }) => {
+ const id = record[NOTE.ID];
+ const title = record[NOTE.TITLE];
+ const body = record[NOTE.BODY];
+ const tags = record[NOTE.TAGS];
+ const date = new Date(record[NOTE.DATE]).toLocaleDateString();
+
+ function truncate(str, n){
+ return (str.length > n) ? str.substr(0, n-1) + '…' : str;
+ }
+
+ return (
+
+
+
+
+
+
+ }
+ title={truncate(title, MAX_TITLE_LENGTH)}
+ subheader={date}
+ />
+
+
+ {truncate(body, MAX_BODY_LENGTH)}
+
+
+ {tags.map((tag, ind) => {
+ if (ind < 2) {
+ return ;
+ } else {
+ const more = `${tags.length - 2} more`
+ return
+ }
+
+ })}
+
+
+
+
+
+ )
+}
+
+export default NoteBoxField;
\ No newline at end of file
diff --git a/pharmd-app/src/components/Fields/NoteListField.js b/pharmd-app/src/components/Fields/NoteListField.js
index f2faaffb..7ee79f0c 100644
--- a/pharmd-app/src/components/Fields/NoteListField.js
+++ b/pharmd-app/src/components/Fields/NoteListField.js
@@ -1,7 +1,12 @@
import React from "react";
-import { Error, Loading, useQuery } from "react-admin";
+import { useDispatch } from "react-redux";
+import { setNotesModal } from "../../redux/actions";
+import { useQuery, Loading, Error } from "react-admin";
import tw, { styled } from "twin.macro";
import NoteField from "./NoteField";
+import IconButton from "@material-ui/core/IconButton";
+import AddCircleOutlineOutlinedIcon from '@material-ui/icons/AddCircleOutlineOutlined';
+import NoteIcon from "../Basic/NoteIcon";
const Label = styled.h1`
${tw`fontStyle-6 font-medium inline-flex`}
@@ -32,6 +37,12 @@ const NoteListField = ({ record = {}, source }) => {
}
});
+ const dispatch = useDispatch();
+
+ function addNote() {
+ dispatch(setNotesModal({ isOpen: true }))
+ }
+
if (loading) return ;
if (error) return ;
if (!data) return null;
@@ -39,9 +50,13 @@ const NoteListField = ({ record = {}, source }) => {
return (
- {data.map(note => {
- return ;
- })}
+
+
+
+ {data.map((note, ind) => {
+ return
+ })
+ }
);
};
diff --git a/pharmd-app/src/components/Layout/NoteCreate.js b/pharmd-app/src/components/Layout/NoteCreate.js
new file mode 100644
index 00000000..2888c4c3
--- /dev/null
+++ b/pharmd-app/src/components/Layout/NoteCreate.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import { Create, TextInput, SimpleForm, required } from 'react-admin';
+
+const NoteCreate = props => {
+ // Input --> Record
+ const parseInput = input => input && input.split(" ")
+
+ // Record --> Input
+ const formatInput = record => record && record.join(" ")
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default NoteCreate;
diff --git a/pharmd-app/src/components/Layout/PharmDModal.js b/pharmd-app/src/components/Layout/PharmDModal.js
new file mode 100644
index 00000000..63b4934d
--- /dev/null
+++ b/pharmd-app/src/components/Layout/PharmDModal.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import Modal from '@material-ui/core/Modal';
+import { styled } from "twin.macro";
+import PropTypes from "prop-types";
+
+const ModalFrame = styled(Modal)`
+ margin-top: 10%;
+ margin-bottom: 10%;
+ margin-left: 15%;
+ margin-right: 15%;
+
+`
+
+const InnerModal = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ color: ${props => props.theme.palette.primary.main};
+ background-color: white;
+ padding: 20px;
+`
+
+const PharmDModal = props => {
+
+ return (
+
+
+ {typeof props.title === "string" ?
+ {props.title}
+ : props.title
+ }
+ {props.children}
+
+
+ )
+}
+
+PharmDModal.propTypes = {
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
+ open: PropTypes.bool,
+ onClose: PropTypes.func,
+}
+
+PharmDModal.defaultProps = {
+ title: "",
+ open: false,
+ onClose: null
+}
+
+export default PharmDModal;
\ No newline at end of file
diff --git a/pharmd-app/src/redux/actions/index.js b/pharmd-app/src/redux/actions/index.js
new file mode 100644
index 00000000..8849cd64
--- /dev/null
+++ b/pharmd-app/src/redux/actions/index.js
@@ -0,0 +1,5 @@
+import { UI_NOTES_MODAL } from "../constants/action-types";
+
+export function setNotesModal(payload) {
+ return { type: UI_NOTES_MODAL, payload}
+}
diff --git a/pharmd-app/src/redux/constants/action-types.js b/pharmd-app/src/redux/constants/action-types.js
new file mode 100644
index 00000000..2ce8b8a5
--- /dev/null
+++ b/pharmd-app/src/redux/constants/action-types.js
@@ -0,0 +1 @@
+export const UI_NOTES_MODAL = "UI_MODAL";
\ No newline at end of file
diff --git a/pharmd-app/src/redux/reducers/index.js b/pharmd-app/src/redux/reducers/index.js
new file mode 100644
index 00000000..d895a12d
--- /dev/null
+++ b/pharmd-app/src/redux/reducers/index.js
@@ -0,0 +1,7 @@
+import studentSideBarReducer from "./studentSideBarReducer";
+import notesModalReducer from "./notesModalReducer";
+
+export default {
+ studentSidebarOpen: studentSideBarReducer,
+ notesModalOpen: notesModalReducer
+}
\ No newline at end of file
diff --git a/pharmd-app/src/redux/reducers/notesModalReducer.js b/pharmd-app/src/redux/reducers/notesModalReducer.js
new file mode 100644
index 00000000..9b23024d
--- /dev/null
+++ b/pharmd-app/src/redux/reducers/notesModalReducer.js
@@ -0,0 +1,8 @@
+import { UI_NOTES_MODAL} from "../constants/action-types";
+
+export default (previousState = [false, () => {}], {type, payload}) => {
+ if (type === UI_NOTES_MODAL) {
+ return [payload.isOpen, payload.onClose]
+ }
+ return previousState;
+}
diff --git a/pharmd-app/src/screens/student/StudentNoteDrawer.js b/pharmd-app/src/screens/student/StudentNoteDrawer.js
new file mode 100644
index 00000000..ceaeed0f
--- /dev/null
+++ b/pharmd-app/src/screens/student/StudentNoteDrawer.js
@@ -0,0 +1,207 @@
+import React, {cloneElement, useState} from 'react'
+import tw, { styled } from "twin.macro";
+import { useDispatch } from "react-redux";
+import MuiGrid from "@material-ui/core/Grid";
+import HelpIcon from "@material-ui/icons/HelpOutline";
+import LeftIcon from '@material-ui/icons/ChevronLeftOutlined';
+import RightIcon from '@material-ui/icons/ChevronRightOutlined';
+import AddIcon from '@material-ui/icons/AddCircleOutlineOutlined';
+import {
+ List,
+ useListContext,
+ TopToolbar,
+ CreateButton,
+ ExportButton,
+ ListBase,
+ ListToolbar,
+ BulkActionsToolbar,
+ Button,
+ sanitizeListRestProps,
+ Pagination,
+ useQuery,
+ Loading,
+ Error
+} from "react-admin";
+import NoteBoxField from "../../components/Fields/NoteBoxField";
+import MuiPaper from "@material-ui/core/Paper";
+import IconButton from '@material-ui/core/IconButton';
+import NoteIcon from "../../components/Basic/NoteIcon";
+import {setNotesModal} from "../../redux/actions";
+
+const NotesActions = (props) => {
+ const {
+ className,
+ exporter,
+ filters,
+ maxResults,
+ ...rest
+ } = props;
+ const {
+ currentSort,
+ resource,
+ displayedFilters,
+ filterValues,
+ hasCreate,
+ basePath,
+ selectedIds,
+ showFilter,
+ total,
+ } = useListContext();
+ return (
+
+
+
+ {/*Custom Action*/}
+
+
+ )
+}
+
+const notesExporter = (records, fetchRelatedRecords) => {
+
+}
+
+const NoteGrid = styled(MuiGrid)`
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: space-around;
+
+`
+
+const Paper = styled(MuiPaper)`
+ ${tw`rounded-xl shadow-cardLight`}
+ padding: 15px;
+ margin: 20px;
+ `
+
+const NoteDrawerContainer = styled(Paper)`
+
+`
+
+const NotesTitle = styled.h2`
+ color: ${props => props.theme.palette.secondary.main};
+`
+
+const NotesHeader = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+`
+
+const NoNotes = styled.h3`
+ text-align: center
+`
+
+const StudentNoteDrawer = ({record = {}, source }) => {
+ const [page, setPage] = useState(0);
+ const dispatch = useDispatch();
+
+ const { data, loading, error } = useQuery({
+ type: 'getManyReference',
+ resource: 'notes',
+ payload: {
+ target: 'student',
+ id: record[source],
+ pagination: {
+ page: 1,
+ perPage: 3
+ },
+ sort: {
+ field: '',
+ order: ''
+ }
+ }
+ })
+
+ function getDataPage(data) {
+ const cursor = page*3;
+ return data.slice(cursor, cursor + 3);
+ }
+ const noNotes = () => data.length === 0;
+ const totalPages = () => Math.ceil(data.length / 3)
+ const onLastPage = () => page === totalPages() - 1;
+ const onFirstPage = () => page === 0;
+
+ function pagesDisplay() {
+ return data.length > 0 ? `${page + 1} / ${totalPages()}` : '0 / 0'
+ }
+
+ function flipPage(isIncrement) {
+ isIncrement ? setPage(page + 1) : setPage(page - 1);
+ }
+
+ function updateNotes() {
+ // TODO how is this done???
+ }
+
+ function addNote() {
+ dispatch(setNotesModal({ isOpen: true, onClose: updateNotes }))
+ }
+
+ if (loading) return
+ if (error) return
+ if (!data) return null
+
+ return (
+
+
+ Notes
+
+
+
+
+ flipPage(false)} disabled={onFirstPage() || noNotes()}>
+
+
+ flipPage(true)} disabled={onLastPage() || noNotes()}>
+
+
+ {pagesDisplay()}
+
+
+ {data.length > 0 ?
+
+ {getDataPage(data).map((note, ind) => {
+ return
+ })}
+
+ : No notes to show!
+ }
+
+ )
+}
+
+const StudentNoteDrawer2 = ({ source, ...props }) => {
+ return (
+
+ {/**/}
+ }
+ bulkActionButtons={false}
+ >
+ Notes
+
+
+ {props.bulkActionButtons}
+
+
+
+ {/**/}
+
+ )
+}
+
+export default StudentNoteDrawer;
\ No newline at end of file
diff --git a/pharmd-app/src/screens/studentDetails/StudentDetailsScreen.js b/pharmd-app/src/screens/studentDetails/StudentDetailsScreen.js
index e96d1091..41d6d356 100644
--- a/pharmd-app/src/screens/studentDetails/StudentDetailsScreen.js
+++ b/pharmd-app/src/screens/studentDetails/StudentDetailsScreen.js
@@ -1,13 +1,18 @@
-import React from "react";
+import React, {Fragment} from "react";
-import tw, { styled } from "twin.macro";
+import tw, {styled} from "twin.macro";
import Paper from "@material-ui/core/Paper";
-import { Loading, useGetOne } from "react-admin";
import AppBar from "../../components/Nav/AppBar";
import StudentDetailsSide from "./StudentDetailsSide";
+import {Loading, useGetOne} from "react-admin";
import StudentDetailsContentGrid from "./StudentDetailsContentGrid";
+import StudentNoteDrawer from "../student/StudentNoteDrawer";
+import PharmDModal from "../../components/Layout/PharmDModal";
+import {setNotesModal} from "../../redux/actions";
+import NoteCreate from "../../components/Layout/NoteCreate";
+import { useSelector, useDispatch } from "react-redux";
const MainContent = styled.div`
${tw`p-12 pt-2 `}
@@ -25,25 +30,35 @@ const SideContent = styled(Paper)`
`;
const StudentDetailsScreen = props => {
- const { data, loading, error } = useGetOne("students", props.match.params.id);
- if (loading) {
- return ;
- }
- if (error) {
- return
Error, id:{id} is not found
;
- }
- return (
- <>
-
-
-
-
-
-
-
-
- >
- );
+ const {data, loading, error} = useGetOne('students', props.match.params.id);
+ const dispatch = useDispatch();
+ const [isNotesModalOpen, onNotesClose] = useSelector(state => state.notesModalOpen)
+ if (loading) {
+ return ;
+ }
+ if (error) {
+ return Error, id: {id} is not found
;
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+ dispatch(setNotesModal({isOpen: false}))}>
+ {
+ onNotesClose();
+ return dispatch(setNotesModal({isOpen: false}));
+ }}/>
+
+
+ );
};
export default StudentDetailsScreen;
diff --git a/pharmd-app/src/stories/notefield.stories.js b/pharmd-app/src/stories/notefield.stories.js
new file mode 100644
index 00000000..88306083
--- /dev/null
+++ b/pharmd-app/src/stories/notefield.stories.js
@@ -0,0 +1,28 @@
+import NoteField from "../components/Fields/NoteField";
+import { action } from '@storybook/addon-actions';
+import React from "react";
+
+export default {
+ title: "NoteField",
+ component: NoteField
+}
+
+const note1 = {
+ text: 'FooFooFoo',
+ title: "FooTitle",
+ created: new Date(2018, 8, 10),
+ lastEdit: new Date(2018, 9, 10),
+ id: 1
+}
+
+const note2 = {
+ text: 'BarBarBar',
+ title: "BarTitle",
+ created: new Date(2018, 8, 10),
+ lastEdit: new Date(2020, 8, 15),
+ id: 2
+}
+
+export const Default = () => ;
+
+export const Note2 = () => ;
\ No newline at end of file
diff --git a/pharmd-app/src/stories/studentNoteBox.stories.js b/pharmd-app/src/stories/studentNoteBox.stories.js
new file mode 100644
index 00000000..402692ac
--- /dev/null
+++ b/pharmd-app/src/stories/studentNoteBox.stories.js
@@ -0,0 +1,26 @@
+import React from "react";
+import StudentNoteBox from "../screens/student/StudentNoteBox";
+
+export default {
+ title: "Student Note Box",
+ component: StudentNoteBox,
+};
+
+const note1 = {
+ id: 1,
+ date: new Date(),
+ tags: [],
+ title: "Fun Note!",
+ body: "Four score and seven years ago..."
+}
+
+const note2 = {
+ id: 2,
+ date: new Date(2000, 10, 3),
+ tags: ["Urgent", "Extreme", "Expired"],
+ title: "Fun Note!",
+ body: "We the people, in order to form a more perfect Union..."
+}
+
+export const Note1 = () =>
+export const Note2 = () =>