From 2e1bd7c87ab5c83961fc68a1c2ceaa381b4e5867 Mon Sep 17 00:00:00 2001 From: Johannes Qian Date: Sun, 27 Oct 2024 21:19:26 -0400 Subject: [PATCH 01/26] Fix profile and signup bug --- server/mongodb/actions/User.ts | 17 ++++++++--------- src/components/AccountEditModal/Profile.tsx | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index a727f17a..e48b2d78 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -118,16 +118,15 @@ export const volunteerSignUp = async ( const chapterObject: IChapter | null = await Chapter.findOne({ name: chapter, }); - if (!chapterObject) { - throw Error("Chapter does not exist"); - } - const updateFilter = { - $inc: { - activeVolunteers: 1, - }, - }; + if (chapterObject) { + const updateFilter = { + $inc: { + activeVolunteers: 1, + }, + }; - await Chapter.updateOne({ name: chapter }, updateFilter); + await Chapter.updateOne({ name: chapter }, updateFilter); + } return result; }; type UParam = { diff --git a/src/components/AccountEditModal/Profile.tsx b/src/components/AccountEditModal/Profile.tsx index e388529e..5ebe4a0e 100644 --- a/src/components/AccountEditModal/Profile.tsx +++ b/src/components/AccountEditModal/Profile.tsx @@ -231,7 +231,7 @@ export default function Profile() {
- {imageLink ? ( + {imageLink || tempImageLink ? ( Profile Image Date: Sun, 3 Nov 2024 00:35:45 -0400 Subject: [PATCH 02/26] 137 Bugfixes and Chapter Managment (#141) * implemented add volunteer modal and its operation success popup, fixed operation success sizing issue * fixed live dropdowns to handle selection changes for certain edge cases * fixed pagination * fixed pendingApprovals not updating correctly, added conditional notif bubble * approve and deny success popups implemented * added all success popups for volunteer actions, added refreshUsers to add volunteer * correctly rename variables * implemented success popup for editing account * implemented success popup for editing account * Fix formatting with rows and unecessary commas --------- Co-authored-by: Johannes Qian --- README.md | 3 +- .../chapter/[name]/page.tsx | 6 +- src/app/(management-portal)/layout.module.css | 5 + src/app/(management-portal)/layout.tsx | 13 +- .../volunteer/approval/page.tsx | 2 +- .../volunteer/search/page.module.css | 5 + .../AccountEditModal/AccountEditModal.tsx | 9 +- src/components/AccountEditModal/Password.tsx | 7 +- src/components/AccountEditModal/Profile.tsx | 8 +- .../AddChapterModal/AddChapterModal.tsx | 2 + .../AddVolunteerModal.module.css | 32 +- .../AddVolunteerModal/AddVolunteerModal.tsx | 384 +++++++----------- src/components/ChapterGrid/Row/Row.tsx | 4 +- .../ChapterInfo/ChapterInfo.module.css | 10 +- src/components/ChapterInfo/ChapterInfo.tsx | 16 + .../VolunteerGrid.module.css | 5 + .../ChapterVolunteerGrid/VolunteerGrid.tsx | 10 + .../NavigationPanel/NavigationPanel.tsx | 4 +- .../OperationSuccessModal.module.css | 6 +- src/components/Pagination/Pagination.tsx | 3 +- .../TransferChapterModal.tsx | 2 + .../VolunteerApprovalGrid/Row/Row.tsx | 16 +- .../VolunteerApprovalGrid.module.css | 5 + .../VolunteerApprovalGrid.tsx | 15 +- src/components/VolunteerGrid/Popup/Popup.tsx | 2 +- .../VolunteerGrid/VolunteerGrid.module.css | 5 + .../VolunteerGrid/VolunteerGrid.tsx | 10 + 27 files changed, 309 insertions(+), 280 deletions(-) diff --git a/README.md b/README.md index a75bc6c8..2a370a7d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Brain Exercise Initative 🧠 ## About -Brain Exercise Initative is a nonprofit focused on preventing memory loss through brain exercise. Through innovative research in Japan, it was found that doing simple math and reading exercises aloud caused improvements in cognitive function. Brain Exercise Initiative builds off of this research, holding brain exercise programs at retirement homes. +Brain Exercise Initative is a nonprofit focused on preventing memory loss through brain exercise. Through innovative research in Japan, it was found that doing simple math and reading exercises aloud caused improvements in cognitive function. Brain Exercise Initiative builds off of this research, holding brain exercise programs at retirement homes. ## Getting Started @@ -20,7 +20,6 @@ yarn install Install [MongoDB Community Server](https://www.mongodb.com/docs/manual/administration/install-community/) to host a local instance of MongoDB. It may also be helpful to download [MongoDB Compass](https://www.mongodb.com/try/download/compass#compass) to view the state of your database. - ### Environment Variables In the root directory, run one of these commands based on your OS: diff --git a/src/app/(management-portal)/chapter/[name]/page.tsx b/src/app/(management-portal)/chapter/[name]/page.tsx index daf6313d..0af920ac 100644 --- a/src/app/(management-portal)/chapter/[name]/page.tsx +++ b/src/app/(management-portal)/chapter/[name]/page.tsx @@ -139,7 +139,11 @@ export default function Page({ params }: { params: { name: string } }) {
- +
diff --git a/src/app/(management-portal)/layout.module.css b/src/app/(management-portal)/layout.module.css index f94b0a09..24388b2c 100644 --- a/src/app/(management-portal)/layout.module.css +++ b/src/app/(management-portal)/layout.module.css @@ -26,3 +26,8 @@ width: 85%; height: 85%; } + +.operationSuccessModal { + width: 35%; + height: 23%; +} diff --git a/src/app/(management-portal)/layout.tsx b/src/app/(management-portal)/layout.tsx index 40ae1621..4ae767b1 100644 --- a/src/app/(management-portal)/layout.tsx +++ b/src/app/(management-portal)/layout.tsx @@ -11,10 +11,12 @@ import useAuth from "@src/hooks/useAuth"; import { RootState } from "@src/redux/rootReducer"; import Modal from "@src/components/Modal/Modal"; +import OperationSuccessModal from "@src/components/OperationSuccessModal/OperationSuccessModal"; import styles from "./layout.module.css"; export default function Layout({ children }: { children: React.ReactNode }) { const [showModal, setShowModal] = useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); const dispatch = useDispatch(); const { email } = useSelector((state: RootState) => state.auth); const { logout } = useAuth(); @@ -50,7 +52,16 @@ export default function Layout({ children }: { children: React.ReactNode }) {
- + + + + {children}
diff --git a/src/app/(management-portal)/volunteer/approval/page.tsx b/src/app/(management-portal)/volunteer/approval/page.tsx index 1ca3c662..1bf8f837 100644 --- a/src/app/(management-portal)/volunteer/approval/page.tsx +++ b/src/app/(management-portal)/volunteer/approval/page.tsx @@ -73,7 +73,7 @@ export default function Page() { }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredUsers(res?.data ?? []); - dispatch(update({ pendingApprovals: res?.numRecords })); + dispatch(update({ pendingApprovals: res?.numRecords ?? 0 })); setLoading(false); }); }, [ diff --git a/src/app/(management-portal)/volunteer/search/page.module.css b/src/app/(management-portal)/volunteer/search/page.module.css index 71af38a3..430c4a79 100644 --- a/src/app/(management-portal)/volunteer/search/page.module.css +++ b/src/app/(management-portal)/volunteer/search/page.module.css @@ -63,3 +63,8 @@ width: 100%; height: 100%; } + +.operationSuccessModal { + width: 35%; + height: 23%; +} diff --git a/src/components/AccountEditModal/AccountEditModal.tsx b/src/components/AccountEditModal/AccountEditModal.tsx index c233d1f0..09fd20fe 100644 --- a/src/components/AccountEditModal/AccountEditModal.tsx +++ b/src/components/AccountEditModal/AccountEditModal.tsx @@ -15,9 +15,10 @@ const enum Page { interface Props { className?: string; style?: CSSProperties; + setShowSuccessModal: (args: boolean) => void; } -const Modal = ({ className, style }: Props) => { +const Modal = ({ className, style, setShowSuccessModal }: Props) => { const [page, setPage] = useState(Page.PROFILE); const { logout } = useAuth(); const router = useRouter(); @@ -51,7 +52,11 @@ const Modal = ({ className, style }: Props) => {
{/* × */} - {page === Page.PROFILE ? : } + {page === Page.PROFILE ? ( + + ) : ( + + )}
); diff --git a/src/components/AccountEditModal/Password.tsx b/src/components/AccountEditModal/Password.tsx index 77f90c2c..0c142724 100644 --- a/src/components/AccountEditModal/Password.tsx +++ b/src/components/AccountEditModal/Password.tsx @@ -9,7 +9,11 @@ import { import styles from "./AccountEditModal.module.css"; import InputField from "../InputField/InputField"; -export default function Password() { +interface Props { + setShowSuccessModal: (args: boolean) => void; +} + +export default function Password({ setShowSuccessModal }: Props) { const [oldPassword, setOldPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmNewPassword, setConfirmNewPassword] = useState(""); @@ -74,6 +78,7 @@ export default function Password() { } await updatePassword(getAuth().currentUser!, newPassword); + setShowSuccessModal(true); reset(); }; diff --git a/src/components/AccountEditModal/Profile.tsx b/src/components/AccountEditModal/Profile.tsx index 5ebe4a0e..b23b2940 100644 --- a/src/components/AccountEditModal/Profile.tsx +++ b/src/components/AccountEditModal/Profile.tsx @@ -13,7 +13,11 @@ import { HttpMethod, IUser } from "@/common_utils/types"; import styles from "./AccountEditModal.module.css"; import Chip from "../Chip/Chip"; -export default function Profile() { +interface Props { + setShowSuccessModal: (args: boolean) => void; +} + +export default function Profile({ setShowSuccessModal }: Props) { const [edit, setEdit] = useState(false); const dispatch = useDispatch(); const router = useRouter(); @@ -146,7 +150,6 @@ export default function Profile() { }); setTempImageLink(null); - // Logout user if email is changed so they can reauthenticate if (updatedEmail !== email) { await logout(); @@ -156,6 +159,7 @@ export default function Profile() { dispatch(update(updatedUser)); setUnupdatedBirthDate(updatedBirthDate); setEdit(false); + setShowSuccessModal(true); }, [ dispatch, logout, diff --git a/src/components/AddChapterModal/AddChapterModal.tsx b/src/components/AddChapterModal/AddChapterModal.tsx index 957c939c..10728cf1 100644 --- a/src/components/AddChapterModal/AddChapterModal.tsx +++ b/src/components/AddChapterModal/AddChapterModal.tsx @@ -123,6 +123,8 @@ const AddChapterModal = ({ type ChangeHandler = React.ChangeEventHandler; const handleChange: ChangeHandler = (e) => { const { target } = e; + setChapterPresidentObject(null); + if (!target.value.trim()) return setFilteredVolunteers([]); const filteredValue = volunteers?.filter((volunteer) => diff --git a/src/components/AddVolunteerModal/AddVolunteerModal.module.css b/src/components/AddVolunteerModal/AddVolunteerModal.module.css index ca618d19..292c4e4e 100644 --- a/src/components/AddVolunteerModal/AddVolunteerModal.module.css +++ b/src/components/AddVolunteerModal/AddVolunteerModal.module.css @@ -36,8 +36,17 @@ font-style: normal; font-weight: 600; line-height: normal; + margin-bottom: -1%; +} + +.inputSubheader { + color: var(--Dark-Blue-Grey, #9ca5c2); + /* H2 */ + font-size: 22px; + font-style: normal; + font-weight: 600; + line-height: normal; margin-bottom: 2%; - gap: 1fr; } .inputField { @@ -55,27 +64,6 @@ font-size: 12px; } -.locationField { - display: flex; - flex-direction: column; -} - -.cityStateFields { - display: flex; - column-gap: 10px; -} - -.cityStateFields > * { - /* flex: 1 */ - width: calc((70% - 10px) / 2); -} - -@media (max-width: 1000px) { - .cityStateFields { - flex-direction: column; - } -} - .nonEditable { background-color: #e7eeff; padding-left: 15px; diff --git a/src/components/AddVolunteerModal/AddVolunteerModal.tsx b/src/components/AddVolunteerModal/AddVolunteerModal.tsx index 864ace1c..b578d42f 100644 --- a/src/components/AddVolunteerModal/AddVolunteerModal.tsx +++ b/src/components/AddVolunteerModal/AddVolunteerModal.tsx @@ -1,268 +1,176 @@ import { CSSProperties, - // FormEvent, - // MouseEvent, - // useState, - // useEffect, + FormEvent, + MouseEvent, + useState, + useEffect, } from "react"; import { classes } from "@src/utils/utils"; -// import { useSelector } from "react-redux"; -// import AuthDropdown from "@src/components/Dropdown/AuthDropdown/AuthDropdown"; import XIcon from "@/src/app/icons/XIcon"; -// import { RootState } from "@src/redux/rootReducer"; -// import { internalRequest } from "@src/utils/requests"; -// import { -// AdminApprovalStatus, -// HttpMethod, -// IVolunteerTableEntry, -// SearchResponseBody, -// } from "@/common_utils/types"; -// import { VolunteerSearchParams, SearchRequestBody } from "@/common_utils/types"; -// import { PostReq } from "@server/mongodb/actions/Chapter"; -// import LiveSearchDropdown from "../LiveSearchDropdown/LiveSearchDropdown"; -// import InputField from "../InputField/InputField"; +import { + AdminApprovalStatus, + HttpMethod, + IChapter, + IVolunteerTableEntry, + Role, + SearchResponseBody, +} from "@/common_utils/types"; +import { PatchReq } from "@src/app/api/volunteer/route"; +import { internalRequest } from "@src/utils/requests"; +import LiveSearchDropdown from "../LiveSearchDropdown/LiveSearchDropdown"; import styles from "./AddVolunteerModal.module.css"; interface Props { className?: string; style?: CSSProperties; setShowModal: (newShowModal: boolean) => void; + setShowSuccessModal: (arg: boolean) => void; + chapter: IChapter; + refreshUsers: () => void; } -const addChapterModal = ({ className, style, setShowModal }: Props) => { - // const [chapterName, setChapterName] = useState(""); - // const [chapterPresident, setChapterPresident] = useState(""); - // const [chapterPresidentID, setChapterPresidentID] = useState(""); - - // const [chapterNameError, setChapterNameError] = useState(""); - // const [chapterPresidentError, setChapterPresidentError] = useState(""); - - // const [locCountry, setLocCountry] = useState(""); - // const [locState, setLocState] = useState(""); - // const [locCity, setLocCity] = useState(""); - - // const [countryError, setCountryError] = useState(""); - // const [stateError, setStateError] = useState(""); - // const [cityError, setCityError] = useState(""); - - // const [volunteers, setVolunteers] = useState(); - // const [filteredVolunteers, setFilteredVolunteers] = useState(); - // const [loading, setLoading] = useState(false); - - // const COUNTRIES = Country.getAllCountries().map((country) => ({ - // value: country.name, - // displayValue: `${country.name}`, - // })); - // const countryCode = Country.getAllCountries().filter( - // (country) => country.name === locCountry, - // )[0]?.isoCode; - - // const STATES = State.getStatesOfCountry(countryCode).map((state) => ({ - // value: state.name, - // displayValue: `${state.name}`, - // })); - - // const stateCode = State.getStatesOfCountry(countryCode).filter( - // (state) => state.name === locState, - // )[0]?.isoCode; - - // const CITIES = City.getCitiesOfState(countryCode, stateCode).map((city) => ({ - // value: city.name, - // displayValue: `${city.name}`, - // })); - - // const resetErrors = () => { - // setChapterNameError(""); - // setChapterPresidentError(""); - // setCountryError(""); - // setStateError(""); - // setCityError(""); - // }; - - // const reset = () => { - // setChapterName(""); - // setChapterPresident(""); - // setChapterPresidentID(""); - // setLocCountry("") - // setLocState(""); - // setLocCity(""); - // resetErrors(); - // }; - - // // Getting Volunteers - // const { - // fullName, - // volunteerRoles, - // } = useSelector((state: RootState) => state.volunteerSearch); - - // useEffect(() => { - // setLoading(true) - // internalRequest>({ - // url: "/api/volunteer/filter-volunteer", - // method: HttpMethod.POST, - // body: { - // params: { - // name: fullName, - // roles: volunteerRoles, - // approved: [AdminApprovalStatus.APPROVED], - // }, - // entriesPerPage: 9999, - // }, - // }).then((res) => { - // setVolunteers(res?.data ?? []); - // setLoading(false) - // }); - // }, []); - - // type changeHandler = React.ChangeEventHandler; - // const handleChange: changeHandler = (e) => { - // const { target } = e; - // if (!target.value.trim()) return setFilteredVolunteers([]); - - // const filteredValue = volunteers?.filter((volunteer) => - // (volunteer.firstName + " " + volunteer.lastName).toLowerCase().startsWith(target.value.toLowerCase()) - // ); - // setFilteredVolunteers(filteredValue); - // }; - - // const handleSaveChanges = async ( - // e: FormEvent | MouseEvent, - // ) => { - // e.preventDefault(); - // resetErrors(); - // let error = false; - - // if (chapterName === "") { - // setChapterNameError("Chapter name cannot be blank"); - // error = true; - // } - - // if (chapterPresident === "") { - // setChapterPresidentError("Choose a valid chapter president"); - // error = true; - // } - - // if (locCountry === "") { - // setCountryError("Country cannot be blank"); - // error = true; - // } - - // if (error) { - // console.log(error); - // return; - // } - - // try { - // console.log(chapterName, chapterPresident, chapterPresidentID, locCountry) - // await internalRequest({ - // url: "/api/chapter", - // method: HttpMethod.POST, - // body: { - // name: chapterName, - // chapterPresident: chapterPresidentID, - // yearFounded: new Date().getFullYear(), - // country: locCountry, - // city: locCity, - // state: locState, - // }, - // }); - // } catch (error) { - // console.log(error) - // } - - // setShowModal(false); - // reset(); - // setChapterCreated(chapterName) - // setShowSuccessModal(true); - // }; +const AddVolunteerModal = ({ + className, + style, + setShowModal, + setShowSuccessModal, + chapter, + refreshUsers, +}: Props) => { + const [newVolunteer, setNewVolunteer] = useState(""); + const [newVolunteerObject, setNewVolunteerObject] = + useState(null); + + const [newVolunteerError, setNewVolunteerError] = useState(""); + + const [volunteers, setVolunteers] = useState(); + const [filteredVolunteers, setFilteredVolunteers] = + useState(); + const [loading, setLoading] = useState(false); + + const resetErrors = () => { + setNewVolunteerError(""); + }; + + const reset = () => { + setNewVolunteer(""); + setNewVolunteerObject(null); + resetErrors(); + }; + + useEffect(() => { + setLoading(true); + internalRequest>({ + url: "/api/volunteer/filter-volunteer", + method: HttpMethod.POST, + body: { + params: { + roles: [Role.NONPROFIT_VOLUNTEER], + approved: [AdminApprovalStatus.APPROVED], + beiChapters: [""], + }, + entriesPerPage: 9999, + }, + }).then((res) => { + setVolunteers(res?.data ?? []); + setLoading(false); + }); + }, []); + + type ChangeHandler = React.ChangeEventHandler; + const handleChange: ChangeHandler = (e) => { + const { target } = e; + setNewVolunteerObject(null); + + if (!target.value.trim()) return setFilteredVolunteers([]); + + const filteredValue = volunteers?.filter((volunteer) => + `${volunteer.firstName} ${volunteer.lastName}` + .toLowerCase() + .startsWith(target.value.toLowerCase()), + ); + return setFilteredVolunteers(filteredValue); + }; + + const handleSaveChanges = async ( + e: FormEvent | MouseEvent, + ) => { + e.preventDefault(); + resetErrors(); + let error = false; + + if (newVolunteer === "") { + setNewVolunteerError("Choose a valid volunteer"); + error = true; + } + + if (newVolunteerObject === null) { + setNewVolunteerError("Choose a valid volunteer"); + error = true; + } + + if (error) { + console.log(error); + return; + } + + try { + await internalRequest({ + url: "/api/volunteer", + method: HttpMethod.PATCH, + body: { + email: newVolunteerObject?.email, + newFields: { + chapter: chapter.name, + }, + }, + }); + } catch (err) { + console.log(err); + } + + setShowModal(false); + reset(); + refreshUsers(); + setShowSuccessModal(true); + }; return (
- {/* <> + <>
- -
-
- - setChapterName(e.target.value)} - showError={chapterNameError !== ""} - error={chapterNameError} - /> +
- -
- { - setLocCountry(e.target.value); - setLocState(""); - setLocCity(""); - setCountryError(""); - setStateError(""); - setCityError(""); - }} - showError={countryError !== ""} - error={countryError} - /> +
+
- {locCountry === "" ? null : ( -
- { - setLocState(e.target.value); - setLocCity(""); - setStateError(""); - setCityError(""); - }} - showError={stateError !== ""} - error={stateError} - /> - { - setLocCity(e.target.value); - setCityError(""); - }} - showError={cityError !== ""} - error={cityError} - /> -
- )}
- + -

{item.firstName + " " + item.lastName + " " + item.phoneNumber}

} + value={newVolunteer} + setValue={setNewVolunteer} + placeholder={ + loading ? "Loading.." : "Enter the name of the volunteer" + } + renderItem={(item) => ( +

+ {`${item.firstName} ${item.lastName} ${item.email}`} +

+ )} onChange={handleChange} onSelect={(item) => { - setChapterPresident(item.firstName + " " + item.lastName) - setChapterPresidentID(item._id)} - } - showError={chapterPresidentError !== ""} - error={chapterPresidentError} + setNewVolunteer(`${item.firstName} ${item.lastName}`); + setNewVolunteerObject(item); + }} + showError={newVolunteerError !== ""} + error={newVolunteerError} />
+
- */} + @@ -288,4 +196,4 @@ const addChapterModal = ({ className, style, setShowModal }: Props) => { ); }; -export default addChapterModal; +export default AddVolunteerModal; diff --git a/src/components/ChapterGrid/Row/Row.tsx b/src/components/ChapterGrid/Row/Row.tsx index 16966a0f..23996a66 100644 --- a/src/components/ChapterGrid/Row/Row.tsx +++ b/src/components/ChapterGrid/Row/Row.tsx @@ -39,7 +39,9 @@ export function Row({ chapter }: Props) { styles.nameCellContainer, )} > - {`${chapter.location.state}, ${chapter.location.country}`} + {chapter.location.state + ? `${chapter.location.state}, ${chapter.location.country}` + : chapter.location.country}
diff --git a/src/components/ChapterInfo/ChapterInfo.module.css b/src/components/ChapterInfo/ChapterInfo.module.css index 3cf7d26b..43f03e79 100644 --- a/src/components/ChapterInfo/ChapterInfo.module.css +++ b/src/components/ChapterInfo/ChapterInfo.module.css @@ -65,9 +65,15 @@ } .addVolunteerModalContent { - width: 60%; - height: 70%; + width: 50%; + height: 45%; } + +.addVolunteerOperationSuccessModal { + width: 35%; + height: 30%; +} + /* .backButton { flex-direction: row; color:#2b3674; diff --git a/src/components/ChapterInfo/ChapterInfo.tsx b/src/components/ChapterInfo/ChapterInfo.tsx index 27f1cb39..9e0cd525 100644 --- a/src/components/ChapterInfo/ChapterInfo.tsx +++ b/src/components/ChapterInfo/ChapterInfo.tsx @@ -29,6 +29,7 @@ import TransferChapterModal from "../TransferChapterModal/TransferChapterModal"; interface ChapterInfoProps { chapter: IChapter; chapterPresident: string; + refreshUsers: () => void; } export default function ChapterInfo(params: ChapterInfoProps) { @@ -44,6 +45,8 @@ export default function ChapterInfo(params: ChapterInfoProps) { useState(false); const [showAddVolunteerModal, setShowAddVolunteerModal] = useState(false); + const [showAddVolunteerSuccessModal, setShowAddVolunteerSuccessModal] = + useState(false); const chapterProfile = useMemo(() => { return [ @@ -222,6 +225,19 @@ export default function ChapterInfo(params: ChapterInfoProps) { + + +
diff --git a/src/components/ChapterVolunteerGrid/VolunteerGrid.module.css b/src/components/ChapterVolunteerGrid/VolunteerGrid.module.css index 5bb35116..f0e35036 100644 --- a/src/components/ChapterVolunteerGrid/VolunteerGrid.module.css +++ b/src/components/ChapterVolunteerGrid/VolunteerGrid.module.css @@ -93,3 +93,8 @@ .atLimit { visibility: hidden; } + +.operationSuccessModal { + width: 35%; + height: 23%; +} diff --git a/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx b/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx index 0ee1d989..2ab82359 100644 --- a/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx +++ b/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx @@ -10,6 +10,8 @@ import TwoVolunteersIcon from "@src/app/icons/TwoVolunteersIcon"; import styles from "./VolunteerGrid.module.css"; import Popup from "./Popup/Popup"; import { Row } from "./Row/Row"; +import Modal from "../Modal/Modal"; +import OperationSuccessModal from "../OperationSuccessModal/OperationSuccessModal"; interface VolunteerGridProps { data: IUser[]; @@ -64,6 +66,7 @@ export default function VolunteerGrid(params: VolunteerGridProps) { const [removeVolunteerEmail, setRemoveVolunteerEmail] = useState< string | null >(null); + const [showSuccessModal, setShowSuccessModal] = useState(false); const handleConfirmDelete = async () => { if (removeVolunteerEmail !== null) { @@ -82,6 +85,7 @@ export default function VolunteerGrid(params: VolunteerGridProps) { params.refreshUsers(); } setPopupOpen(false); + setShowSuccessModal(true); }; const handleClosePopup = useCallback(() => { @@ -125,6 +129,12 @@ export default function VolunteerGrid(params: VolunteerGridProps) { pageCount={params.pageCount} currentPage={params.currentPage} /> + + +
); } diff --git a/src/components/NavigationPanel/NavigationPanel.tsx b/src/components/NavigationPanel/NavigationPanel.tsx index 5f2e5a52..fc725bf3 100644 --- a/src/components/NavigationPanel/NavigationPanel.tsx +++ b/src/components/NavigationPanel/NavigationPanel.tsx @@ -228,7 +228,9 @@ const NavigationPanel = ({ onClick }: Props) => {
Pending Approval
-
{pendingApprovals}
+ {pendingApprovals > 0 && ( +
{pendingApprovals}
+ )}
diff --git a/src/components/OperationSuccessModal/OperationSuccessModal.module.css b/src/components/OperationSuccessModal/OperationSuccessModal.module.css index 37cf508c..96911f9f 100644 --- a/src/components/OperationSuccessModal/OperationSuccessModal.module.css +++ b/src/components/OperationSuccessModal/OperationSuccessModal.module.css @@ -16,8 +16,10 @@ } .checkCircle { - width: 38px; - height: 38px; + width: 45px; + height: 45px; + min-width: 38px; + min-height: 38px; } .bigText { diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index 69154e85..9b81ef6a 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -10,9 +10,10 @@ interface DataParams { const Pagination = (params: DataParams) => { const pages = useMemo(() => { const forwardPages: number[] = []; + const numForwardPages = params.currentPage === 0 ? 4 : 3; for ( let i = params.currentPage + 1; - i <= params.pageCount && forwardPages.length !== 4; + i <= params.pageCount && forwardPages.length !== numForwardPages; i += 1 ) { forwardPages.push(i); diff --git a/src/components/TransferChapterModal/TransferChapterModal.tsx b/src/components/TransferChapterModal/TransferChapterModal.tsx index feebc9b3..c39d45a7 100644 --- a/src/components/TransferChapterModal/TransferChapterModal.tsx +++ b/src/components/TransferChapterModal/TransferChapterModal.tsx @@ -85,6 +85,8 @@ const TransferChapterModal = ({ type ChangeHandler = React.ChangeEventHandler; const handleChange: ChangeHandler = (e) => { const { target } = e; + setChapterPresidentObject(null); + if (!target.value.trim()) return setFilteredVolunteers([]); const filteredValue = volunteers?.filter((volunteer) => diff --git a/src/components/VolunteerApprovalGrid/Row/Row.tsx b/src/components/VolunteerApprovalGrid/Row/Row.tsx index 456e70d3..ece9c0de 100644 --- a/src/components/VolunteerApprovalGrid/Row/Row.tsx +++ b/src/components/VolunteerApprovalGrid/Row/Row.tsx @@ -26,6 +26,8 @@ import styles from "./Row.module.css"; interface Props { volunteer: IUser; refreshUsers: () => void; + setShowModal: (arg: boolean) => void; + setSuccessMessage: (arg: string) => void; } function ExpandedRow({ row }: { row: IUser }) { @@ -103,7 +105,12 @@ function ExpandedRow({ row }: { row: IUser }) { ); } -export function Row({ volunteer, refreshUsers }: Props) { +export function Row({ + volunteer, + refreshUsers, + setShowModal, + setSuccessMessage, +}: Props) { const [view, setView] = useState(false); const handleClick = useCallback(() => setView((v) => !v), []); // Role of user corresponding to the given row @@ -136,6 +143,13 @@ export function Row({ volunteer, refreshUsers }: Props) { }, }); refreshUsers(); + if (approvalStatus) { + setSuccessMessage("Volunteer Successfully Approved"); + setShowModal(true); + } else { + setSuccessMessage("Volunteer Successfully Denied"); + setShowModal(true); + } }; return ( diff --git a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.module.css b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.module.css index 3c709c71..9bc42241 100644 --- a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.module.css +++ b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.module.css @@ -55,3 +55,8 @@ .atLimit { visibility: hidden; } + +.operationSuccessModal { + width: 35%; + height: 23%; +} diff --git a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx index 2cf3f5ba..dcdd697d 100644 --- a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx +++ b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import DataGrid from "@src/components/DataGrid/DataGrid"; import Pagination from "@src/components/Pagination/Pagination"; import { IUser, SortField } from "@/common_utils/types"; @@ -9,6 +9,8 @@ import TwoVolunteersIcon from "@src/app/icons/TwoVolunteersIcon"; import { Row } from "./Row/Row"; import styles from "./VolunteerApprovalGrid.module.css"; +import Modal from "../Modal/Modal"; +import OperationSuccessModal from "../OperationSuccessModal/OperationSuccessModal"; interface VolunteerApprovalGridProps { data: IUser[]; @@ -52,6 +54,9 @@ function Header() { export default function VolunteerApprovalGrid( params: VolunteerApprovalGridProps, ) { + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [successMessage, setSuccessMessage] = useState(""); + // Construct Rows from the volunteers const Rows = params.data.map((volunteer) => { return ( @@ -59,6 +64,8 @@ export default function VolunteerApprovalGrid( key={`volunteer-${volunteer._id}`} volunteer={volunteer} refreshUsers={params.refreshUsers} + setShowModal={setShowSuccessModal} + setSuccessMessage={setSuccessMessage} /> ); }); @@ -80,6 +87,12 @@ export default function VolunteerApprovalGrid( pageCount={params.pageCount} currentPage={params.currentPage} /> + + + ); } diff --git a/src/components/VolunteerGrid/Popup/Popup.tsx b/src/components/VolunteerGrid/Popup/Popup.tsx index 0107076d..4b8514d9 100644 --- a/src/components/VolunteerGrid/Popup/Popup.tsx +++ b/src/components/VolunteerGrid/Popup/Popup.tsx @@ -11,7 +11,7 @@ const Popup = ({ return (
-
Remove from Chapter
+
Delete Account
Deleting this account will remove all of this user's information from the database. This cannot be undone. diff --git a/src/components/VolunteerGrid/VolunteerGrid.module.css b/src/components/VolunteerGrid/VolunteerGrid.module.css index 5bb35116..f0e35036 100644 --- a/src/components/VolunteerGrid/VolunteerGrid.module.css +++ b/src/components/VolunteerGrid/VolunteerGrid.module.css @@ -93,3 +93,8 @@ .atLimit { visibility: hidden; } + +.operationSuccessModal { + width: 35%; + height: 23%; +} diff --git a/src/components/VolunteerGrid/VolunteerGrid.tsx b/src/components/VolunteerGrid/VolunteerGrid.tsx index 689eee01..f4fecbda 100644 --- a/src/components/VolunteerGrid/VolunteerGrid.tsx +++ b/src/components/VolunteerGrid/VolunteerGrid.tsx @@ -10,6 +10,8 @@ import TwoVolunteersIcon from "@src/app/icons/TwoVolunteersIcon"; import styles from "./VolunteerGrid.module.css"; import Popup from "./Popup/Popup"; import { Row } from "./Row/Row"; +import Modal from "../Modal/Modal"; +import OperationSuccessModal from "../OperationSuccessModal/OperationSuccessModal"; interface VolunteerGridProps { data: IUser[]; @@ -64,6 +66,7 @@ export default function VolunteerGrid(params: VolunteerGridProps) { const [deleteVolunteerEmail, setDeleteVolunteerEmail] = useState< string | null >(null); + const [showSuccessModal, setShowSuccessModal] = useState(false); const handleConfirmDelete = async () => { if (deleteVolunteerEmail !== null) { @@ -79,6 +82,7 @@ export default function VolunteerGrid(params: VolunteerGridProps) { params.refreshUsers(); } setPopupOpen(false); + setShowSuccessModal(true); }; const handleClosePopup = useCallback(() => { @@ -122,6 +126,12 @@ export default function VolunteerGrid(params: VolunteerGridProps) { pageCount={params.pageCount} currentPage={params.currentPage} /> + + +
); } From 6fd56dd3a1d977ff3214a140e5e0d0e13b2e3c01 Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:12:16 -0400 Subject: [PATCH 03/26] bugfixes (#142) * bugfixes * Add chapter option for patients --------- Co-authored-by: Johannes Qian Co-authored-by: Johannes Qian <115521998+johannesq23@users.noreply.github.com> --- server/mongodb/actions/User.ts | 5 ++- src/app/api/patient/auth/signup/route.ts | 4 +- src/app/api/patient/internal/seed/route.ts | 2 +- src/app/api/volunteer/auth/signup/route.ts | 3 ++ src/app/api/volunteer/route.ts | 4 +- src/app/auth/admin-approval/page.tsx | 2 + src/app/auth/email-verification/page.tsx | 45 ++++++++++--------- src/app/auth/information/page.tsx | 18 ++++++-- .../AddChapterModal/AddChapterModal.tsx | 18 ++++++-- .../EditChapterModal/EditChapterModal.tsx | 18 ++++++-- .../Search/AdvancedSearch/AdvancedSearch.tsx | 18 ++++++-- .../VolunteerAdvancedSearch.tsx | 18 ++++++-- 12 files changed, 111 insertions(+), 44 deletions(-) diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index e48b2d78..0d2896ee 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -62,7 +62,7 @@ export const createUserEmail = async (email: string): Promise => { }; export const patientSignUp = async ( - data: Omit, + data: Omit, ): Promise => { const result = await User.findOneAndUpdate( { email: data.email }, @@ -71,6 +71,7 @@ export const patientSignUp = async ( firstName: data.firstName, lastName: data.lastName, phoneNumber: data.phoneNumber, + chapter: data.chapter, birthDate: new Date(data.birthDate), patientDetails: { secondaryContactName: data.patientDetails.secondaryContactName, @@ -94,6 +95,7 @@ export const volunteerSignUp = async ( state: string, city: string, chapter: string, + role: string, ): Promise => { const result = await User.findOneAndUpdate( { email }, @@ -109,6 +111,7 @@ export const volunteerSignUp = async ( city, }, chapter, + role, }, }, diff --git a/src/app/api/patient/auth/signup/route.ts b/src/app/api/patient/auth/signup/route.ts index fbdec801..0a76efd7 100644 --- a/src/app/api/patient/auth/signup/route.ts +++ b/src/app/api/patient/auth/signup/route.ts @@ -8,6 +8,7 @@ interface SignupData { lastName: string; phoneNumber: string; birthDate: string; + chapter?: string; secondaryContactName: string; secondaryContactPhone: string; } @@ -50,6 +51,7 @@ export const POST = APIWrapper({ lastName: signupData.lastName, phoneNumber: signupData.phoneNumber, birthDate: signupData.birthDate, + chapter: signupData.chapter ? signupData.chapter : "", patientDetails: { secondaryContactName: signupData.secondaryContactName, secondaryContactPhone: signupData.secondaryContactPhone, @@ -57,7 +59,7 @@ export const POST = APIWrapper({ }, signedUp: true, role: Role.NONPROFIT_PATIENT, - } as Omit); + } as Omit); return newSignUp; }, }); diff --git a/src/app/api/patient/internal/seed/route.ts b/src/app/api/patient/internal/seed/route.ts index 4ceef1ab..df62132f 100644 --- a/src/app/api/patient/internal/seed/route.ts +++ b/src/app/api/patient/internal/seed/route.ts @@ -29,7 +29,7 @@ export const POST = APIWrapper({ await createAnalyticsID(user._id); }), ); - // for (let i = 750; i < 10000; i++) { + // for (let i = 1; i < 10000; i++) { // await Promise.all( // sampleUsers.map(async (user: IUser) => { // let deluser = await User.findOneAndDelete({ diff --git a/src/app/api/volunteer/auth/signup/route.ts b/src/app/api/volunteer/auth/signup/route.ts index f7a6670c..76f2a288 100644 --- a/src/app/api/volunteer/auth/signup/route.ts +++ b/src/app/api/volunteer/auth/signup/route.ts @@ -1,3 +1,4 @@ +import { Role } from "@/common_utils/types"; import { volunteerSignUp } from "@server/mongodb/actions/User"; import APIWrapper from "@server/utils/APIWrapper"; @@ -10,6 +11,7 @@ type SignupData = { state: string; city: string; chapter: string; + role?: string; }; export const POST = APIWrapper({ @@ -54,6 +56,7 @@ export const POST = APIWrapper({ signupData.state, signupData.city, signupData.chapter, + signupData.role || Role.NONPROFIT_VOLUNTEER, ); updateCookie?.push({ user: newSignUp!, keepLogged: false }); diff --git a/src/app/api/volunteer/route.ts b/src/app/api/volunteer/route.ts index 37408dc2..cac63ccf 100644 --- a/src/app/api/volunteer/route.ts +++ b/src/app/api/volunteer/route.ts @@ -67,7 +67,9 @@ export const PATCH = APIWrapper({ } const user = await updateVolunteer(email, newFields); - updateCookie?.push({ user: user! }); + if (newFields.email !== null && email === newFields.email) { + updateCookie?.push({ user: user! }); + } return user; }, }); diff --git a/src/app/auth/admin-approval/page.tsx b/src/app/auth/admin-approval/page.tsx index 6d4bbf11..d1bd835e 100644 --- a/src/app/auth/admin-approval/page.tsx +++ b/src/app/auth/admin-approval/page.tsx @@ -6,6 +6,7 @@ import { AdminApprovalStatus, HttpMethod, IUser } from "@/common_utils/types"; import { internalRequest } from "@src/utils/requests"; import { useSelector } from "react-redux"; import { RootState } from "@src/redux/rootReducer"; +import { deleteCookie } from "cookies-next"; import styles from "./page.module.css"; const Page = () => { @@ -28,6 +29,7 @@ const Page = () => { if (user.approved === AdminApprovalStatus.APPROVED) { router.push("/patient/search"); } + deleteCookie("authUser"); }); }, [router, email]); diff --git a/src/app/auth/email-verification/page.tsx b/src/app/auth/email-verification/page.tsx index 2a49d25b..4bd7c378 100644 --- a/src/app/auth/email-verification/page.tsx +++ b/src/app/auth/email-verification/page.tsx @@ -8,6 +8,7 @@ import { HttpMethod } from "@/common_utils/types"; import firebaseInit from "@src/firebase/config"; import { useSelector } from "react-redux"; import { RootState } from "@src/redux/rootReducer"; +import { deleteCookie } from "cookies-next"; import styles from "./page.module.css"; @@ -28,28 +29,32 @@ export default function Page() { const router = useRouter(); const [loadingState, setLoadingState] = useState(State.LOADING); const { email } = useSelector((rootState: RootState) => rootState.auth); + const [response, setResponse] = useState(); - const verifyEmail = useCallback( - async (emailToVerify: string | null) => { - try { - const res: Response = await internalRequest({ - url: "/api/volunteer/auth/email-verification/create", - method: HttpMethod.POST, - body: { - email: emailToVerify, - }, - }); - if (res.verified === true) { - router.push("/auth/information"); - } else { - setLoadingState(State.SUCCESS); - } - } catch (err) { - setLoadingState(State.ERROR); + const verifyEmail = useCallback(async (emailToVerify: string | null) => { + try { + const res: Response = await internalRequest({ + url: "/api/volunteer/auth/email-verification/create", + method: HttpMethod.POST, + body: { + email: emailToVerify, + }, + }); + setResponse(res); + if (res.verified !== true) { + setLoadingState(State.SUCCESS); + deleteCookie("authUser"); } - }, - [router], - ); + } catch (err) { + setLoadingState(State.ERROR); + } + }, []); + + useEffect(() => { + if (response?.verified === true) { + router.push("/auth/information"); + } + }, [response, router]); useEffect(() => { verifyEmail(email); diff --git a/src/app/auth/information/page.tsx b/src/app/auth/information/page.tsx index baed3c60..8515df18 100644 --- a/src/app/auth/information/page.tsx +++ b/src/app/auth/information/page.tsx @@ -42,10 +42,20 @@ export default function Page() { const router = useRouter(); - const COUNTRIES = Country.getAllCountries().map((country) => ({ - value: country.name, - displayValue: `${country.name}`, - })); + const COUNTRIES = Country.getAllCountries() + .sort((a, b) => { + if (a.name === "United States") { + return -1; + } + if (b.name === "United States") { + return 1; + } + return 0; + }) + .map((country) => ({ + value: country.name, + displayValue: `${country.name}`, + })); const countryCode = Country.getAllCountries().filter( (country) => country.name === locCountry, )[0]?.isoCode; diff --git a/src/components/AddChapterModal/AddChapterModal.tsx b/src/components/AddChapterModal/AddChapterModal.tsx index 10728cf1..866b3af7 100644 --- a/src/components/AddChapterModal/AddChapterModal.tsx +++ b/src/components/AddChapterModal/AddChapterModal.tsx @@ -62,10 +62,20 @@ const AddChapterModal = ({ useState(); const [loading, setLoading] = useState(false); - const COUNTRIES = Country.getAllCountries().map((country) => ({ - value: country.name, - displayValue: `${country.name}`, - })); + const COUNTRIES = Country.getAllCountries() + .sort((a, b) => { + if (a.name === "United States") { + return -1; + } + if (b.name === "United States") { + return 1; + } + return 0; + }) + .map((country) => ({ + value: country.name, + displayValue: `${country.name}`, + })); const countryCode = Country.getAllCountries().filter( (country) => country.name === locCountry, )[0]?.isoCode; diff --git a/src/components/EditChapterModal/EditChapterModal.tsx b/src/components/EditChapterModal/EditChapterModal.tsx index f9c2e6d7..12c0b169 100644 --- a/src/components/EditChapterModal/EditChapterModal.tsx +++ b/src/components/EditChapterModal/EditChapterModal.tsx @@ -59,10 +59,20 @@ const EditChapterModal = ({ [], ); - const COUNTRIES = Country.getAllCountries().map((country) => ({ - value: country.name, - displayValue: `${country.name}`, - })); + const COUNTRIES = Country.getAllCountries() + .sort((a, b) => { + if (a.name === "United States") { + return -1; + } + if (b.name === "United States") { + return 1; + } + return 0; + }) + .map((country) => ({ + value: country.name, + displayValue: `${country.name}`, + })); const countryCode = Country.getAllCountries().filter( (country) => country.name === locCountry, )[0]?.isoCode; diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index c25fb3b6..65483ad8 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -249,10 +249,20 @@ export const AdvancedSearch = (props: UpdateParamProp) => { } }; - const COUNTRIES = Country.getAllCountries().map((locCountry) => ({ - value: locCountry.name, - displayValue: `${locCountry.name}`, - })); + const COUNTRIES = Country.getAllCountries() + .sort((a, b) => { + if (a.name === "United States") { + return -1; + } + if (b.name === "United States") { + return 1; + } + return 0; + }) + .map((locCountry) => ({ + value: locCountry.name, + displayValue: `${locCountry.name}`, + })); const countryCode = Country.getAllCountries().filter( (locCountry) => country === locCountry.name, )[0]?.isoCode; diff --git a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx index 20e32787..21be64b2 100644 --- a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx +++ b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx @@ -226,10 +226,20 @@ export const VolunteerAdvancedSearch = (props: UpdateParamProp) => { } }; - const COUNTRIES = Country.getAllCountries().map((locCountry) => ({ - value: locCountry.name, - displayValue: `${locCountry.name}`, - })); + const COUNTRIES = Country.getAllCountries() + .sort((a, b) => { + if (a.name === "United States") { + return -1; + } + if (b.name === "United States") { + return 1; + } + return 0; + }) + .map((locCountry) => ({ + value: locCountry.name, + displayValue: `${locCountry.name}`, + })); const countryCode = Country.getAllCountries().filter( (locCountry) => country === locCountry.name, )[0]?.isoCode; From a2bc6fb22611a862fdf265becc5ec03b42682fe6 Mon Sep 17 00:00:00 2001 From: Johannes Qian Date: Sun, 3 Nov 2024 01:16:36 -0400 Subject: [PATCH 04/26] Fix updateCookie logic --- src/app/api/volunteer/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/volunteer/route.ts b/src/app/api/volunteer/route.ts index cac63ccf..bb81e2e2 100644 --- a/src/app/api/volunteer/route.ts +++ b/src/app/api/volunteer/route.ts @@ -67,7 +67,7 @@ export const PATCH = APIWrapper({ } const user = await updateVolunteer(email, newFields); - if (newFields.email !== null && email === newFields.email) { + if (testuser?._id === currentUser?._id) { updateCookie?.push({ user: user! }); } return user; From 88245dfd6a0058e62ea52b4c94f5e3612b49bbde Mon Sep 17 00:00:00 2001 From: Johannes Qian <115521998+johannesq23@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:14:33 -0500 Subject: [PATCH 05/26] Drb bugfixes (#152) * - Fix snapping to different sections in patient analytics - Disable sidebar upon modal click - Filtering Fixed- the filter actually worked the whole time, and it was returning the correct information- the volunteer filter produces more because more data is filled out than in the patient search - Removed Chapter Image section * Fix graphs and add tooltips * remove barChart --- src/app/(management-portal)/layout.module.css | 5 + src/app/(management-portal)/layout.tsx | 5 +- .../patient/analytics/[id]/page.tsx | 25 +- src/components/ChapterInfo/ChapterInfo.tsx | 8 +- .../Dashboard/MathScreen/MathScreen.tsx | 4 +- .../Dashboard/ReadingScreen/ReadingScreen.tsx | 1 - .../Dashboard/TriviaScreen/TriviaScreen.tsx | 1 - src/components/DateSelector/DateSelector.tsx | 4 +- .../Graphs/BarChart/BarChart.module.scss | 2 + src/components/Graphs/BarChart/BarChart.tsx | 227 ++++++++++-------- .../Graphs/LineChart/LineChart.module.scss | 2 +- src/components/Graphs/LineChart/LineChart.tsx | 169 +++++++------ .../StackedBarChart/StackedBarChart.tsx | 40 +-- .../GroupMathScreen/GroupMathScreen.tsx | 2 - .../GroupReadingScreen/GroupReadingScreen.tsx | 1 - .../GroupTriviaScreen/GroupTriviaScreen.tsx | 1 - .../NavigationPanel.module.css | 5 + .../NavigationPanel/NavigationPanel.tsx | 19 +- 18 files changed, 281 insertions(+), 240 deletions(-) diff --git a/src/app/(management-portal)/layout.module.css b/src/app/(management-portal)/layout.module.css index 24388b2c..c77cf27d 100644 --- a/src/app/(management-portal)/layout.module.css +++ b/src/app/(management-portal)/layout.module.css @@ -31,3 +31,8 @@ width: 35%; height: 23%; } + +.disabled { + pointer-events: none; + opacity: 0.8; /* Optional: add a visual indicator */ +} diff --git a/src/app/(management-portal)/layout.tsx b/src/app/(management-portal)/layout.tsx index 4ae767b1..8a9f3181 100644 --- a/src/app/(management-portal)/layout.tsx +++ b/src/app/(management-portal)/layout.tsx @@ -48,7 +48,10 @@ export default function Layout({ children }: { children: React.ReactNode }) { return (
- setShowModal(!showModal)} /> + setShowModal(!showModal)} + modalOpen={showModal} + />
diff --git a/src/app/(management-portal)/patient/analytics/[id]/page.tsx b/src/app/(management-portal)/patient/analytics/[id]/page.tsx index 480081d2..93dad815 100644 --- a/src/app/(management-portal)/patient/analytics/[id]/page.tsx +++ b/src/app/(management-portal)/patient/analytics/[id]/page.tsx @@ -1,13 +1,6 @@ "use client"; -import React, { - useEffect, - useRef, - useState, - useCallback, - useMemo, -} from "react"; -import useHashObserver from "@src/hooks/useHashObserver"; +import React, { useEffect, useState, useCallback } from "react"; import { OverallDashboard, MathScreen, @@ -55,14 +48,6 @@ export function Divider({ id }: { id?: string }) { } export default function Page({ params }: { params: { id: string } }) { - const mathRef = useRef(null); - const readingRef = useRef(null); - const writingRef = useRef(null); - const triviaRef = useRef(null); - - const refs = useMemo(() => [mathRef, readingRef, writingRef, triviaRef], []); - useHashObserver(refs); - const [math, setMath] = useState< IAggregatedAnalyticsMath["math"] | undefined >(undefined); @@ -235,7 +220,7 @@ export default function Page({ params }: { params: { id: string } }) { />
-
+
-
+
-
+
-
+
Chapter Profile

-
+ {/*
-
+ /> */} + {/*
*/}
{chapterProfile.map((cell) => ( diff --git a/src/components/Dashboard/MathScreen/MathScreen.tsx b/src/components/Dashboard/MathScreen/MathScreen.tsx index ad58f626..142a109d 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.tsx +++ b/src/components/Dashboard/MathScreen/MathScreen.tsx @@ -60,7 +60,6 @@ const MathScreen = ({ title="Average Math Accuracy" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={accuracyData} fullWidth @@ -73,7 +72,6 @@ const MathScreen = ({ title="Average Math Difficulty" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={difficultyData} fullWidth @@ -89,6 +87,7 @@ const MathScreen = ({ yLabel="Questions" fullWidth gridLines + info="test" />
diff --git a/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx b/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx index 6d2f153b..9fa62ecd 100644 --- a/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx +++ b/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx @@ -105,7 +105,6 @@ export default function ReadingScreen({ title="Average Reading Rate (Words/Min)" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={readingRate} fullWidth diff --git a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx index e0d8bb52..62d368ac 100644 --- a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx +++ b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx @@ -68,7 +68,6 @@ export default function TriviaScreen({ title="Average Trivia Accuracy" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={accuracyData} fullWidth diff --git a/src/components/DateSelector/DateSelector.tsx b/src/components/DateSelector/DateSelector.tsx index 6eecc676..1e2ed0d9 100644 --- a/src/components/DateSelector/DateSelector.tsx +++ b/src/components/DateSelector/DateSelector.tsx @@ -29,7 +29,7 @@ function DateSelector({ setSelectedValue(e.target.value as DateRangeEnum); }} style={{ - borderRadius: 0, + borderRadius: 12, color: "#8d8d8d", border: "none", width: "130px", @@ -38,7 +38,7 @@ function DateSelector({ sx={{ "&.MuiOutlinedInput-root": { "& fieldset": { - borderRadius: "0px", + borderRadius: "12px", }, }, }} diff --git a/src/components/Graphs/BarChart/BarChart.module.scss b/src/components/Graphs/BarChart/BarChart.module.scss index 93d1f784..fb01e276 100644 --- a/src/components/Graphs/BarChart/BarChart.module.scss +++ b/src/components/Graphs/BarChart/BarChart.module.scss @@ -13,6 +13,7 @@ margin: auto; align-items: first baseline; height: 1.25em; + height: auto; .titleText { font-family: var(--font-poppins); font-weight: 500; @@ -44,6 +45,7 @@ cursor: pointer; } .percentageChange { + display: flex; font-family: var(--font-inter); font-weight: 700; margin-left: 12px; diff --git a/src/components/Graphs/BarChart/BarChart.tsx b/src/components/Graphs/BarChart/BarChart.tsx index 68909460..82dca1db 100644 --- a/src/components/Graphs/BarChart/BarChart.tsx +++ b/src/components/Graphs/BarChart/BarChart.tsx @@ -3,15 +3,7 @@ import { Poppins, Inter } from "next/font/google"; import { InfoIcon } from "@src/app/icons"; import * as d3 from "d3"; -import { - Fragment, - MouseEvent, - ReactNode, - useCallback, - useEffect, - useRef, - useState, -} from "react"; +import { MouseEvent, useCallback, useEffect, useRef, useState } from "react"; import { D3Data } from "@src/utils/types"; import { DataRecord } from "@/common_utils/types"; import PopupModal from "../PopupModal/PopupModal"; @@ -28,7 +20,6 @@ interface DataParams extends D3Data { percentageChange?: boolean; highlightLargest?: boolean; yLabel?: string; - children?: ReactNode; gridLines?: boolean; info?: string; } @@ -41,25 +32,18 @@ export default function BarChart({ height: providedHeight = 180, style = {}, yAxis = { - min: - (d3.min(data.map((v) => v.value)) ?? 0) - - 0.1 * - ((d3.max(data.map((v) => v.value)) ?? 1) - - (d3.min(data.map((v) => v.value)) ?? 0)), - max: - (d3.max(data.map((v) => v.value)) ?? 1) + - 0.1 * - ((d3.max(data.map((v) => v.value)) ?? 1) - - (d3.min(data.map((v) => v.value)) ?? 0)) + - 0.000001, - numDivisions: Math.round((Math.max(providedHeight, 100) - 35) / 25), - format: (d: d3.NumberValue) => d3.format(".2f")(d), + min: Math.ceil(0), + max: Math.floor(d3.max(data.map((v) => v.value + v.value / 5)) ?? 1), + numDivisions: Math.min( + 5, + Math.floor(d3.max(data.map((v) => v.value)) ?? 1) + 1, + ), + format: d3.format("d"), }, hoverable = false, percentageChange = false, highlightLargest = true, fullWidth = false, - children, gridLines = false, info = "", yLabel = "", @@ -102,7 +86,6 @@ export default function BarChart({ const marginBottom = 40; const marginLeft = 35; const [largest, setLargest] = useState(-1); - const [activeIndex, setActiveIndex] = useState(-1); const [infoPopup, setInfoPopup] = useState(false); const [popupX, setPopupX] = useState(null); const [popupY, setPopupY] = useState(null); @@ -115,23 +98,6 @@ export default function BarChart({ const windowRef = useRef(null); - function handleMouseMove(e: MouseEvent) { - const x = e.pageX; - const svg: Element = e.currentTarget as SVGElement; - const svgRect = svg.getBoundingClientRect(); - const xBound = svgRect.x + marginLeft; - const range = width - marginRight - marginLeft; - const itemWidth = range / (data.length - 1); - const index = Math.floor( - (x - xBound + Math.floor(itemWidth / 2)) / itemWidth, - ); - setActiveIndex(index); - } - - const handleMouseLeave = () => { - setActiveIndex(-1); - }; - const x = d3.scaleLinear( [0, newData.length - 1], [marginLeft, width - marginRight], @@ -185,6 +151,8 @@ export default function BarChart({ svg.select(".x-axis-top").remove(); svg.select(".x-axis-bottom").remove(); svg.select(".y-axis").remove(); + svg.selectAll(".bar").remove(); + const xAxisLabelTop = d3 .axisBottom(x) .ticks(newData.length) @@ -302,6 +270,90 @@ export default function BarChart({ .call(yAxisLabel) .call((g) => g.select(".domain").remove()); + const barsGroup = svg + .selectAll(".bar") + .data(newData) + .join("g") + .attr("class", "bar") + .attr("transform", (d, i) => `translate(${x(i)}, 0)`); // Position bars + + const tooltip = d3.select("#tooltip"); + + if (hoverable) { + barsGroup + .append("path") + .attr("d", (d) => { + const barHeight = height - marginBottom - y(d.value); + const radius = barWidth / 2; // Radius for the semi-circle top + const x0 = 0; + const y0 = y(d.value); + + // Define the path for a rectangle with a semi-circle top + if (d.value === 0) { + // Render a semi-circle (half-circle) if the value is 0 + return ` + M ${x0},${y0} + A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} + `; + } + // Render a rectangle with a rounded top if the value is non-zero + return ` + M ${x0},${y0 + barHeight} + V ${y0 + radius} + A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} + V ${y0 + barHeight} + H ${x0} Z + `; + }) + .style("fill", (d, i) => + highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", + ) + .on("mouseover", (event: MouseEvent, d: DataRecord) => { + tooltip.transition().duration(0).style("opacity", 1); + tooltip + .html(`${d.value}`) + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + }) + .on("mousemove", (event: MouseEvent) => { + tooltip + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + }) + .on("mouseout", () => { + tooltip.transition().duration(0).style("opacity", 0); + }); + } else { + barsGroup + .append("path") + .attr("d", (d) => { + const barHeight = height - marginBottom - y(d.value); + const radius = barWidth / 2; // Radius for the semi-circle top + const x0 = 0; + const y0 = y(d.value); + + // Define the path for a rectangle with a semi-circle top + if (d.value === 0) { + // Render a semi-circle (half-circle) if the value is 0 + return ` + M ${x0},${y0} + A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} + `; + } + // Render a rectangle with a rounded top if the value is non-zero + return ` + M ${x0},${y0 + barHeight} + V ${y0 + radius} + A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} + V ${y0 + barHeight} + H ${x0} Z + `; + }) + .style("fill", (d, i) => + highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", + ); + } + return () => window.removeEventListener("scroll", onScroll); }, [ newData, @@ -325,27 +377,6 @@ export default function BarChart({ setNewData(updateNewData()); }, [data, updateNewData]); - const HoverableNode = ({ i, d }: { i: number; d: D3Data["data"][0] }) => - activeIndex === i && ( - -
- {d.value} -
-
- ); - return (
{ + onMouseEnter={() => { setInfoPopup(true); }} + onMouseLeave={() => { + setInfoPopup(false); + }} ref={infoButtonRef} + style={{ position: "relative" }} > + {/*
+
{ + setInfoPopup(true); + }} + ref={infoButtonRef} + > */} {yLabel}

+
- - {children || - newData.map((d, i) => { - const color = - highlightLargest && largest === i ? "#FF9FB3" : "#008AFC"; - return ( - - - - - - ); - })} - {newData.map((d, i) => ( - - ))} - + {/* + */}
diff --git a/src/components/Graphs/LineChart/LineChart.module.scss b/src/components/Graphs/LineChart/LineChart.module.scss index 270e40a9..36135097 100644 --- a/src/components/Graphs/LineChart/LineChart.module.scss +++ b/src/components/Graphs/LineChart/LineChart.module.scss @@ -55,7 +55,7 @@ } } .info { - font-size: 15px; + font-size: 12px; margin-top: auto; margin-bottom: auto; margin-left: 12px; diff --git a/src/components/Graphs/LineChart/LineChart.tsx b/src/components/Graphs/LineChart/LineChart.tsx index 1d2f0eaa..d639c685 100644 --- a/src/components/Graphs/LineChart/LineChart.tsx +++ b/src/components/Graphs/LineChart/LineChart.tsx @@ -2,14 +2,7 @@ import { Poppins, Inter } from "next/font/google"; import * as d3 from "d3"; -import { - Fragment, - MouseEvent, - useCallback, - useEffect, - useRef, - useState, -} from "react"; +import { MouseEvent, useCallback, useEffect, useRef, useState } from "react"; import { D3Data } from "@src/utils/types"; import { InfoIcon } from "@src/app/icons"; import { DataRecord } from "@/common_utils/types"; @@ -25,7 +18,6 @@ interface DataParams extends D3Data { title: string; hoverable?: boolean; percentageChange?: boolean; - gradient?: boolean; info?: string; gridLines?: boolean; yLabel?: string; @@ -49,12 +41,11 @@ export default function LineChart({ ((d3.max(data.map((v) => v.value)) ?? 1) - (d3.min(data.map((v) => v.value)) ?? 0)), numDivisions: Math.round((Math.max(providedHeight, 100) - 35) / 25), - format: (d: d3.NumberValue) => d3.format(".2f")(d), + format: (d: d3.NumberValue) => d3.format(".1f")(d), }, title, hoverable = false, percentageChange = false, - gradient = false, fullWidth, info = "", yLabel = "", @@ -96,7 +87,6 @@ export default function LineChart({ const marginRight = 20; const marginBottom = 65; const marginLeft = 40; - const [activeIndex, setActiveIndex] = useState(-1); const [infoPopup, setInfoPopup] = useState(false); const [popupX, setPopupX] = useState(0); const [popupY, setPopupY] = useState(0); @@ -107,23 +97,6 @@ export default function LineChart({ : newData[newData.length - 1].value / newData[newData.length - 2].value - 1; - function handleMouseMove(e: MouseEvent) { - const x = e.pageX; - const svg: Element = e.currentTarget as SVGElement; - const svgRect = svg.getBoundingClientRect(); - const xBound = svgRect.x + marginLeft; - const range = width - marginRight - marginLeft; - const itemWidth = range / (newData.length - 1); - const index = Math.floor( - (x - xBound + Math.floor(itemWidth / 2)) / itemWidth, - ); - setActiveIndex(index); - } - - const handleMouseLeave = () => { - setActiveIndex(-1); - }; - const windowRef = useRef(null); const y = d3.scaleLinear( @@ -165,6 +138,10 @@ export default function LineChart({ svg.select(".x-axis-top").remove(); svg.select(".x-axis-bottom").remove(); svg.select(".y-axis").remove(); + svg.select(".path").remove(); + svg.select(".point").remove(); + svg.selectAll("*").remove(); + const xAxisLabelTop = d3 .axisBottom(x) .ticks(newData.length - 1) @@ -277,6 +254,63 @@ export default function LineChart({ .call(yAxisLabel) .call((g) => g.select(".domain").remove()); + const d3line = line(newData.map((d, i) => [i, d.value])) as string; + + const tooltip = d3.select("#tooltip"); + // Draw the line connecting the points + svg + .append("path") + .datum(newData) // Bind data to the path + .attr("class", styles.linePath) // Add a class for styling + .attr("d", d3line) // Create the line using the defined line function + .style("fill", "none") // No fill for the line + .style("stroke", "#008AFC") // Line color + .style("stroke-width", 5) // Line width + .style("stroke-linecap", "round") + .style("stroke-linejoin", "round") + .style("filter", "url(#drop-shadow)"); + + // Draw points as circles on the line + if (hoverable) { + svg + .selectAll(".point") + .data(newData) + .join("circle") + .attr("class", styles.hoverCircle) + .attr("cx", (d, i) => x(i)) // X position based on index + .attr("cy", (d) => y(d.value)) // Y position based on the value + .attr("r", 7) // Radius of the circles + .style("fill", "white") // Set fill color to white + .style("stroke", "#008AFC") // Set stroke color + .style("stroke-width", 5) // Set stroke width + .style("opacity", 0) // Initially hidden + + .on( + "mouseover", + function handleMouseOver(event: MouseEvent, d: DataRecord) { + tooltip.transition().duration(0).style("opacity", 1); + tooltip + .html(`${d.value}`) + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + + // Show the circle when hovered over the line + d3.select(this).style("opacity", 1); // Set opacity to 1 + }, + ) + .on("mousemove", (event: MouseEvent) => { + tooltip + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + }) + .on("mouseout", function handleMouseOut() { + tooltip.transition().duration(0).style("opacity", 0); + + // Hide the circle when not hovering + d3.select(this).style("opacity", 0); // Set opacity back to 0 + }); + } + return () => window.removeEventListener("scroll", onScroll); }, [ width, @@ -321,10 +355,14 @@ export default function LineChart({ {info !== "" && (
{ + onMouseEnter={() => { setInfoPopup(true); }} + onMouseLeave={() => { + setInfoPopup(false); + }} ref={infoButtonRef} + style={{ position: "relative" }} > {yLabel}

+
- {gradient && ( - - - - )} - [i, d.value])) as string | undefined} - /> - - - + - {newData.map( - (d, i) => - activeIndex === i && ( - - - -
- {d.value} -
-
-
- ), - )} -
+
); diff --git a/src/components/Graphs/StackedBarChart/StackedBarChart.tsx b/src/components/Graphs/StackedBarChart/StackedBarChart.tsx index 063de573..0e742dc9 100644 --- a/src/components/Graphs/StackedBarChart/StackedBarChart.tsx +++ b/src/components/Graphs/StackedBarChart/StackedBarChart.tsx @@ -1,10 +1,9 @@ "use client"; import * as d3 from "d3"; -import { Fragment, useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { D3Data } from "@src/utils/types"; import { DataRecord, StackedDataRecord } from "@/common_utils/types"; -import BarChart from "../BarChart/BarChart"; import styles from "./StackedBarChart.module.scss"; interface DataParams extends D3Data { @@ -73,18 +72,7 @@ export default function StackedBarChart({ window.addEventListener("resize", resizeOptimised); const height = Math.max(providedHeight, 80); - const marginTop = 20; - const marginRight = 25; - const marginBottom = 40; - const marginLeft = 35; - const x = d3.scaleLinear( - [0, data.length - 1], - [marginLeft, width - marginRight], - ); - const y = d3.scaleLinear( - [yAxis.min, yAxis.max], - [height - marginBottom, marginTop], - ); + useEffect(() => { updateSize(); }, [newData, updateSize]); @@ -93,13 +81,27 @@ export default function StackedBarChart({ setNewData(updateNewData()); }, [data, updateNewData]); + console.log( + title, + data, + width, + height, + style, + yAxis, + hoverable, + percentageChange, + info, + yLabel, + gridLines, + ); + return (
- - {data.map((d, i) => ( + > */} + {/* {data.map((d, i) => ( - ))} - + ))} */} + {/* */}
{legend.map((l) => ( diff --git a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx index dadda4db..bdb23302 100644 --- a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx +++ b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx @@ -60,7 +60,6 @@ const GroupMathScreen = ({ title="Average Math Accuracy" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={accuracyData} fullWidth @@ -72,7 +71,6 @@ const GroupMathScreen = ({ title="Average Math Difficulty" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={difficultyData} fullWidth diff --git a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx index 0b7334bb..086dcbcf 100644 --- a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx +++ b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx @@ -101,7 +101,6 @@ export default function GroupReadingScreen({ title="Average Reading Rate (Words/Min)" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={readingRate} fullWidth diff --git a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx index 39c8bb85..8dcec120 100644 --- a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx +++ b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx @@ -68,7 +68,6 @@ export default function GroupTriviaScreen({ title="Average Trivia Accuracy" hoverable={true} percentageChange={true} - gradient={true} info="Vidushi" data={accuracyData} fullWidth diff --git a/src/components/NavigationPanel/NavigationPanel.module.css b/src/components/NavigationPanel/NavigationPanel.module.css index 128d589e..7781e7f6 100644 --- a/src/components/NavigationPanel/NavigationPanel.module.css +++ b/src/components/NavigationPanel/NavigationPanel.module.css @@ -468,3 +468,8 @@ .bottomSection { margin-top: -40px; } + +.disabled { + pointer-events: none; + opacity: 0.6; +} diff --git a/src/components/NavigationPanel/NavigationPanel.tsx b/src/components/NavigationPanel/NavigationPanel.tsx index fc725bf3..b2b019f5 100644 --- a/src/components/NavigationPanel/NavigationPanel.tsx +++ b/src/components/NavigationPanel/NavigationPanel.tsx @@ -22,10 +22,12 @@ import styles from "./NavigationPanel.module.css"; interface Props { onClick: () => void; + modalOpen: boolean; } -const NavigationPanel = ({ onClick }: Props) => { +const NavigationPanel = ({ onClick, modalOpen }: Props) => { const user = useSelector((state) => state.auth) as IUser; + const pendingApprovals = useSelector( (state) => state.generalInfo.pendingApprovals, ) as number; @@ -86,7 +88,7 @@ const NavigationPanel = ({ onClick }: Props) => { if (user.role !== Role.NONPROFIT_VOLUNTEER) { fetchUsers(); } - }, []); + }, [fetchUsers, user.role]); const currentPath = usePathname(); @@ -120,9 +122,16 @@ const NavigationPanel = ({ onClick }: Props) => { IUser >((state) => state.auth); + // const switchModal = () => { + // setIsModalOpen(prevState => !prevState); + // onClick(); + // }; + return (
-
+
{
-
+
{user.role !== Role.NONPROFIT_VOLUNTEER && ( <>
From 3e589ab2556848ab9072909a7a656eaf8a566789 Mon Sep 17 00:00:00 2001 From: Johannes Qian <115521998+johannesq23@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:21:57 -0500 Subject: [PATCH 06/26] Nik/139 extensive patient dashboard testing (#143) * making no data show up on empty graphs * data showing up in graphs now * reversing graphs back * uncommenting sample data * fixing graphs when no data * styling changes * run lint and prettier * total session completion history aggregated * run prettier and lint * Get ready for drb, still missing max feature + sessions + no data found --------- Co-authored-by: nikvijay07 --- server/mongodb/actions/AggregatedAnalytics.ts | 44 ++++++++++--- .../patient/analytics/[id]/page.tsx | 62 +++++++----------- src/app/api/patient/analytics/route.ts | 1 - src/app/icons/HouseIcon.tsx | 2 +- src/app/icons/LoadingIcon.tsx | 4 +- src/app/icons/ProfilePicIcon.tsx | 2 +- src/app/icons/XCircle.tsx | 4 +- src/app/icons/XIcon.tsx | 2 +- .../MathScreen/MathScreen.module.css | 33 ++++++++++ .../Dashboard/MathScreen/MathScreen.tsx | 63 ++++++++++--------- .../OverallDashboard/OverallDashboard.tsx | 4 +- .../ReadingScreen/ReadingScreen.module.css | 32 ++++++++++ .../Dashboard/ReadingScreen/ReadingScreen.tsx | 2 +- .../TriviaScreen/TriviaScreen.module.css | 32 ++++++++++ .../Dashboard/TriviaScreen/TriviaScreen.tsx | 7 ++- .../WritingScreen/WritingScreen.module.css | 32 ++++++++++ .../Graphs/BarChart/BarChart.module.scss | 2 +- src/components/Graphs/BarChart/BarChart.tsx | 35 +++-------- .../Graphs/LineChart/LineChart.module.scss | 10 +-- src/components/Graphs/LineChart/LineChart.tsx | 37 +++++------ .../StackedBarChart/StackedBarChart.tsx | 34 +++++----- 21 files changed, 284 insertions(+), 160 deletions(-) diff --git a/server/mongodb/actions/AggregatedAnalytics.ts b/server/mongodb/actions/AggregatedAnalytics.ts index b060b167..d9e269f9 100644 --- a/server/mongodb/actions/AggregatedAnalytics.ts +++ b/server/mongodb/actions/AggregatedAnalytics.ts @@ -9,8 +9,9 @@ import { IUser, } from "@/common_utils/types"; import { formatDateByRangeEnum, getCurrentMonday } from "@server/utils/utils"; -import Analytics from "../models/Analytics"; +import mongoose from "mongoose"; import User from "../models/User"; +import Analytics from "../models/Analytics"; type TempAggData = Partial<{ [K in AnalyticsSectionEnum]: { @@ -63,8 +64,9 @@ export const getAggregatedAnalytics = async ( numOfWeeks = 52; // 52 } + const objectIdArray = userIDs.map((id) => new mongoose.Types.ObjectId(id)); const userRecords = await User.find( - { userID: { $in: userIDs } }, + { _id: { $in: objectIdArray } }, { weeklyMetrics: { $slice: [1, numOfWeeks] } }, ); @@ -82,7 +84,6 @@ export const getAggregatedAnalytics = async ( const out: Partial[] = []; const lastMonday = new Date(getCurrentMonday().getDate() - 7); - for (let i = 0; i < analyticsRecords.length; i += 1) { const analyticsRecord = analyticsRecords[i] as IAnalytics; const user = userRecords[i]; @@ -99,16 +100,16 @@ export const getAggregatedAnalytics = async ( const paddingDate = reversedWeeklyMetrics.length === 0 ? lastMonday - : new Date(analyticsRecord.weeklyMetrics[0].date); + : new Date(reversedWeeklyMetrics[0].date); let lastDate = reversedWeeklyMetrics.length === 0 ? lastMonday - : new Date(analyticsRecord.weeklyMetrics[0].date); + : new Date(reversedWeeklyMetrics[0].date); let lastDateMax = reversedWeeklyMetrics.length === 0 ? lastMonday - : new Date(analyticsRecord.weeklyMetrics[0].date); + : new Date(reversedWeeklyMetrics[0].date); const dbDateVars = new Set(); @@ -143,9 +144,15 @@ export const getAggregatedAnalytics = async ( const overallObj = groupSumDict[dateVar].overall ?? { totalNum: 0, streakLength: 0, + totalSessionsCompleted: 0, }; overallObj.totalNum += 1; overallObj.streakLength += item.streakLength; + overallObj.totalSessionsCompleted += + item.math.sessionsCompleted + + item.reading.sessionsCompleted + + item.trivia.sessionsCompleted + + item.writing.sessionsCompleted; groupSumDict[dateVar].overall = overallObj; break; } @@ -262,6 +269,12 @@ export const getAggregatedAnalytics = async ( } else if (range === DateRangeEnum.YEAR) { totalWeeks = 12; } + if ( + userIDs.length === 1 && + (analyticsRecords[0].weeklyMetrics as []).length === 0 + ) { + totalWeeks = 0; + } while (len < totalWeeks) { if (range === DateRangeEnum.RECENT || range === DateRangeEnum.QUARTER) { @@ -365,7 +378,6 @@ export const getAggregatedAnalytics = async ( "questionsCorrect", "avgSessionsAttempted", ]); - allDateVars.forEach((month) => { Object.entries(groupSumDict[month]).forEach(([type, monthTypeDict]) => { Object.keys(monthTypeDict).forEach((property) => { @@ -393,7 +405,20 @@ export const getAggregatedAnalytics = async ( return; } - const dr: DataRecord = { + let dr: DataRecord; + + if (property === "totalSessionsCompleted") { + dr = { + interval: month, + value: monthTypeDict.totalSessionsCompleted, + }; + const obj = result.overall; + if (obj) { + obj.streakHistory.push(dr); + } + } + + dr = { interval: month, value: monthTypeDict[property], }; @@ -404,7 +429,7 @@ export const getAggregatedAnalytics = async ( if (!obj[property as keyof typeof obj]) { (obj[property as keyof typeof obj] as DataRecord[]) = [dr]; } else { - (obj[property as keyof typeof obj] as DataRecord[]).push(dr); + (obj[property as keyof typeof obj] as DataRecord[]).unshift(dr); } }); }); @@ -528,7 +553,6 @@ export const getAggregatedAnalytics = async ( break; } }); - out.push(finalAggregation); } diff --git a/src/app/(management-portal)/patient/analytics/[id]/page.tsx b/src/app/(management-portal)/patient/analytics/[id]/page.tsx index 93dad815..8fc0e208 100644 --- a/src/app/(management-portal)/patient/analytics/[id]/page.tsx +++ b/src/app/(management-portal)/patient/analytics/[id]/page.tsx @@ -11,7 +11,6 @@ import { import { AnalyticsSectionEnum, DateRangeEnum, - Days, HttpMethod, IAggregatedAnalyticsAll, IAggregatedAnalyticsMath, @@ -20,13 +19,6 @@ import { IAggregatedAnalyticsTrivia, IAggregatedAnalyticsWriting, } from "@/common_utils/types"; -import { - dataBar, - dataLine, - dataStacked, - numberOfQuestionData, -} from "@src/utils/patients"; - import Modal from "@src/components/Modal/Modal"; import LoadingBox from "@src/components/LoadingBox/LoadingBox"; import { internalRequest } from "@src/utils/requests"; @@ -95,7 +87,7 @@ export default function Page({ params }: { params: { id: string } }) { }); setLoading(false); return data; - } catch { + } catch (error) { setLoading(false); return {} as IAggregatedAnalyticsAll; } @@ -109,11 +101,11 @@ export default function Page({ params }: { params: { id: string } }) { newDateRange, [AnalyticsSectionEnum.OVERALL], ); - setMath(data.math); - setTrivia(data.trivia); - setReading(data.reading); - setWriting(data.writing); - setOverall(data.overall); + setMath(data?.math); + setTrivia(data?.trivia); + setReading(data?.reading); + setWriting(data?.writing); + setOverall(data?.overall); setDashboardMenu(newDateRange); setMathMenu(newDateRange); setReadingMenu(newDateRange); @@ -191,15 +183,7 @@ export default function Page({ params }: { params: { id: string } }) { menuState={[dashboardMenu, updateAllAnalytics]} name={overall?.name ?? "Unknown"} active={overall?.active ?? false} - streak={ - overall?.streak ?? [ - Days.Sunday, - Days.Monday, - Days.Tuesday, - Days.Thursday, - Days.Friday, - ] - } + streak={overall?.streak ?? []} startDate={ overall?.startDate ? new Date(overall.startDate) : new Date() } @@ -216,17 +200,17 @@ export default function Page({ params }: { params: { id: string } }) { triviaQuestionsCompleted: 0, } } - sessionCompletionHistory={overall?.streakHistory ?? dataBar} + sessionCompletionHistory={overall?.streakHistory ?? []} />
= ({ className }) => ( diff --git a/src/app/icons/LoadingIcon.tsx b/src/app/icons/LoadingIcon.tsx index 26d0ca15..bec3724a 100644 --- a/src/app/icons/LoadingIcon.tsx +++ b/src/app/icons/LoadingIcon.tsx @@ -17,8 +17,8 @@ export default function LoadingIcon({ className, style }: Props) { style={style} > diff --git a/src/app/icons/ProfilePicIcon.tsx b/src/app/icons/ProfilePicIcon.tsx index 016636c8..6429c231 100644 --- a/src/app/icons/ProfilePicIcon.tsx +++ b/src/app/icons/ProfilePicIcon.tsx @@ -25,7 +25,7 @@ const ProfilePicIcon = ({ className }: { className?: string }) => ( r="47" stroke="lightgrey" fill="none" - stroke-width="2" + strokeWidth="2" /> diff --git a/src/app/icons/XCircle.tsx b/src/app/icons/XCircle.tsx index d60ec62e..2d53586e 100644 --- a/src/app/icons/XCircle.tsx +++ b/src/app/icons/XCircle.tsx @@ -8,8 +8,8 @@ const XCircle = () => ( fill="none" > diff --git a/src/app/icons/XIcon.tsx b/src/app/icons/XIcon.tsx index 6ffa3673..93f2347e 100644 --- a/src/app/icons/XIcon.tsx +++ b/src/app/icons/XIcon.tsx @@ -10,7 +10,7 @@ const XIcon = () => ( diff --git a/src/components/Dashboard/MathScreen/MathScreen.module.css b/src/components/Dashboard/MathScreen/MathScreen.module.css index 0aad80e9..23d4bd1b 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.module.css +++ b/src/components/Dashboard/MathScreen/MathScreen.module.css @@ -36,6 +36,39 @@ row-gap: 20px; } +.textStatsWithHeader { + flex: 0.9; /* This can be adjusted based on your layout needs */ + display: flex; /* Default to flexbox */ + flex-direction: column; /* Stack children vertically */ + row-gap: 20px; /* Space between items */ +} + +.body { + display: flex; /* Main content container */ + flex-direction: row; /* Align items vertically */ +} + +@media (max-width: 1300px) { + .textStats { + display: grid; /* Switch to grid layout */ + grid-template-columns: repeat(2, 1fr); /* Create 2 equal columns */ + gap: 10px; /* Space between grid items */ + width: 100%; /* Ensure it takes full width */ + margin-bottom: 20px; /* Optional: space below textStats */ + } + + .body { + flex-direction: column-reverse; + margin-top: 20px; /* Optional: space between textStats and body */ + } +} + +.body > .box { + /* Assuming .box is the class for the custom component */ + flex: 0 0 calc(50% - 10px); /* Adjust width when needed */ + margin: 5px; /* Optional: add some spacing */ +} + .graphs { flex: 2; display: grid; diff --git a/src/components/Dashboard/MathScreen/MathScreen.tsx b/src/components/Dashboard/MathScreen/MathScreen.tsx index 142a109d..5b4816de 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.tsx +++ b/src/components/Dashboard/MathScreen/MathScreen.tsx @@ -40,6 +40,9 @@ const MathScreen = ({ style, menuState, }: InputProp) => { + const modifiedAccuracy = `${Math.round(+currentAccuracy * 100)}%`; + const modifiedTime = `${totalTime} seconds`; + return (
@@ -101,36 +104,38 @@ const MathScreen = ({ info="Test" />
-
+

Last Session Breakdown

- - - - +
+ + + + +
diff --git a/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx b/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx index 07ecb67b..4e0325d1 100644 --- a/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx +++ b/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx @@ -42,7 +42,7 @@ interface Params { } const options: Intl.DateTimeFormatOptions = { - weekday: "short", + day: "numeric", year: "numeric", month: "short", }; @@ -50,7 +50,7 @@ const options: Intl.DateTimeFormatOptions = { function formatDate(date: Date) { const str = date.toLocaleDateString("en-us", options); const arr = str.split(" "); - return [arr[2], arr[0], arr[1]].join(" "); + return [arr[0], arr[1], arr[2]].join(" "); } export default function OverallDashboard(params: Params) { diff --git a/src/components/Dashboard/ReadingScreen/ReadingScreen.module.css b/src/components/Dashboard/ReadingScreen/ReadingScreen.module.css index 0aad80e9..9eb4e80a 100644 --- a/src/components/Dashboard/ReadingScreen/ReadingScreen.module.css +++ b/src/components/Dashboard/ReadingScreen/ReadingScreen.module.css @@ -36,6 +36,38 @@ row-gap: 20px; } +.textStatsWithHeader { + flex: 0.9; /* This can be adjusted based on your layout needs */ + display: flex; /* Default to flexbox */ + flex-direction: column; /* Stack children vertically */ + row-gap: 20px; /* Space between items */ +} + +.body { + display: flex; /* Main content container */ + flex-direction: row; /* Align items vertically */ +} + +@media (max-width: 1300px) { + .textStats { + display: grid; /* Switch to grid layout */ + gap: 10px; /* Space between grid items */ + width: 100%; /* Ensure it takes full width */ + margin-bottom: 20px; /* Optional: space below textStats */ + } + + .body { + flex-direction: column-reverse; + margin-top: 20px; /* Optional: space between textStats and body */ + } +} + +.body > .box { + /* Assuming .box is the class for the custom component */ + flex: 0 0 calc(50% - 10px); /* Adjust width when needed */ + margin: 5px; /* Optional: add some spacing */ +} + .graphs { flex: 2; display: grid; diff --git a/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx b/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx index 9fa62ecd..8acb5d75 100644 --- a/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx +++ b/src/components/Dashboard/ReadingScreen/ReadingScreen.tsx @@ -134,7 +134,7 @@ export default function ReadingScreen({ diff --git a/src/components/Dashboard/TriviaScreen/TriviaScreen.module.css b/src/components/Dashboard/TriviaScreen/TriviaScreen.module.css index 0aad80e9..9eb4e80a 100644 --- a/src/components/Dashboard/TriviaScreen/TriviaScreen.module.css +++ b/src/components/Dashboard/TriviaScreen/TriviaScreen.module.css @@ -36,6 +36,38 @@ row-gap: 20px; } +.textStatsWithHeader { + flex: 0.9; /* This can be adjusted based on your layout needs */ + display: flex; /* Default to flexbox */ + flex-direction: column; /* Stack children vertically */ + row-gap: 20px; /* Space between items */ +} + +.body { + display: flex; /* Main content container */ + flex-direction: row; /* Align items vertically */ +} + +@media (max-width: 1300px) { + .textStats { + display: grid; /* Switch to grid layout */ + gap: 10px; /* Space between grid items */ + width: 100%; /* Ensure it takes full width */ + margin-bottom: 20px; /* Optional: space below textStats */ + } + + .body { + flex-direction: column-reverse; + margin-top: 20px; /* Optional: space between textStats and body */ + } +} + +.body > .box { + /* Assuming .box is the class for the custom component */ + flex: 0 0 calc(50% - 10px); /* Adjust width when needed */ + margin: 5px; /* Optional: add some spacing */ +} + .graphs { flex: 2; display: grid; diff --git a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx index 62d368ac..7bac73d0 100644 --- a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx +++ b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx @@ -48,6 +48,9 @@ export default function TriviaScreen({ style, menuState, }: InputProp) { + const modifiedAccuracy = `${Math.round(+currentAccuracy * 100)}%`; + const modifiedTime = `${totalTime} seconds`; + return (
@@ -99,7 +102,7 @@ export default function TriviaScreen({ diff --git a/src/components/Dashboard/WritingScreen/WritingScreen.module.css b/src/components/Dashboard/WritingScreen/WritingScreen.module.css index 0aad80e9..9eb4e80a 100644 --- a/src/components/Dashboard/WritingScreen/WritingScreen.module.css +++ b/src/components/Dashboard/WritingScreen/WritingScreen.module.css @@ -36,6 +36,38 @@ row-gap: 20px; } +.textStatsWithHeader { + flex: 0.9; /* This can be adjusted based on your layout needs */ + display: flex; /* Default to flexbox */ + flex-direction: column; /* Stack children vertically */ + row-gap: 20px; /* Space between items */ +} + +.body { + display: flex; /* Main content container */ + flex-direction: row; /* Align items vertically */ +} + +@media (max-width: 1300px) { + .textStats { + display: grid; /* Switch to grid layout */ + gap: 10px; /* Space between grid items */ + width: 100%; /* Ensure it takes full width */ + margin-bottom: 20px; /* Optional: space below textStats */ + } + + .body { + flex-direction: column-reverse; + margin-top: 20px; /* Optional: space between textStats and body */ + } +} + +.body > .box { + /* Assuming .box is the class for the custom component */ + flex: 0 0 calc(50% - 10px); /* Adjust width when needed */ + margin: 5px; /* Optional: add some spacing */ +} + .graphs { flex: 2; display: grid; diff --git a/src/components/Graphs/BarChart/BarChart.module.scss b/src/components/Graphs/BarChart/BarChart.module.scss index fb01e276..bf1b2c88 100644 --- a/src/components/Graphs/BarChart/BarChart.module.scss +++ b/src/components/Graphs/BarChart/BarChart.module.scss @@ -55,7 +55,7 @@ } .yAxis line { - stroke-width: 1px; + strokewidth: 1px; color: #e3eafc; } .yAxis path { diff --git a/src/components/Graphs/BarChart/BarChart.tsx b/src/components/Graphs/BarChart/BarChart.tsx index 82dca1db..2e391685 100644 --- a/src/components/Graphs/BarChart/BarChart.tsx +++ b/src/components/Graphs/BarChart/BarChart.tsx @@ -51,7 +51,7 @@ export default function BarChart({ const updateNewData = useCallback(() => { const datapoints = 10; if (data.length === 0) { - return [{ interval: "1", value: 1 }]; + return []; } if (data.length > datapoints) { const step = Math.floor(data.length / datapoints); @@ -64,6 +64,7 @@ export default function BarChart({ return data; }, [data]); const [newData, setNewData] = useState(updateNewData()); + const [dataExists, setDataExists] = useState(newData.length !== 0); const barWidth = 12; const minWidth = (barWidth + 5) * newData.length + 60; const [width, setWidth] = useState(Math.max(providedWidth, minWidth)); @@ -91,7 +92,7 @@ export default function BarChart({ const [popupY, setPopupY] = useState(null); const actualChange = - newData.length < 2 + newData.length < 2 || newData[newData.length - 2].value === 0 ? null : newData[newData.length - 1].value / newData[newData.length - 2].value - 1; @@ -159,7 +160,7 @@ export default function BarChart({ .tickSizeOuter(0) .tickSizeInner(0) .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[0]); + .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[0] ?? ""); const xAxisLabelBottom = d3 .axisBottom(x) @@ -167,7 +168,7 @@ export default function BarChart({ .tickSizeOuter(0) .tickSizeInner(0) .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[1]); + .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[1] ?? ""); const yAxisLabel = d3 .axisLeft(y) @@ -367,6 +368,7 @@ export default function BarChart({ yAxis.numDivisions, gridLines, width, + dataExists, ]); useEffect(() => { @@ -375,6 +377,7 @@ export default function BarChart({ useEffect(() => { setNewData(updateNewData()); + setDataExists(newData.length !== 0); }, [data, updateNewData]); return ( @@ -408,14 +411,6 @@ export default function BarChart({ ref={infoButtonRef} style={{ position: "relative" }} > - {/*
-
{ - setInfoPopup(true); - }} - ref={infoButtonRef} - > */} - {/* - */} - + >
-
- {/*
*/} -
+
); diff --git a/src/components/Graphs/LineChart/LineChart.module.scss b/src/components/Graphs/LineChart/LineChart.module.scss index 36135097..a4feec19 100644 --- a/src/components/Graphs/LineChart/LineChart.module.scss +++ b/src/components/Graphs/LineChart/LineChart.module.scss @@ -65,29 +65,29 @@ .linePath { fill: none; stroke: #008afc; - stroke-width: 6; + strokewidth: 6; border-radius: 10px; height: 100%; filter: url(#drop-shadow); } .svgComp { - stroke-width: 1.5; + strokewidth: 1.5; stroke: currentColor; fill: white; circle { - stroke-width: 1; + strokewidth: 1; color: #008afc; fill: #008afc; } .hoverCircle { - stroke-width: 5; + strokewidth: 5; color: #008afc; fill: white; } } .yAxis line { - stroke-width: 1px; + strokewidth: 1px; color: #e3eafc; } .yAxis path { diff --git a/src/components/Graphs/LineChart/LineChart.tsx b/src/components/Graphs/LineChart/LineChart.tsx index d639c685..47623b6f 100644 --- a/src/components/Graphs/LineChart/LineChart.tsx +++ b/src/components/Graphs/LineChart/LineChart.tsx @@ -54,7 +54,7 @@ export default function LineChart({ const updateNewData = useCallback(() => { const datapoints = 10; if (data.length === 0) { - return [{ interval: "1", value: 1 }]; + return []; } if (data.length > datapoints) { const step = Math.floor(data.length / datapoints); @@ -67,6 +67,7 @@ export default function LineChart({ return data; }, [data]); const [newData, setNewData] = useState(updateNewData()); + const [dataExists, setDataExists] = useState(newData.length !== 0); const minWidth = 210; const [width, setWidth] = useState(Math.max(providedWidth, minWidth)); const windowSizeRef = useRef(null); @@ -92,7 +93,7 @@ export default function LineChart({ const [popupY, setPopupY] = useState(0); const actualChange = - newData.length < 2 + newData.length < 2 || newData[newData.length - 2].value === 0 ? null : newData[newData.length - 1].value / newData[newData.length - 2].value - 1; @@ -158,15 +159,18 @@ export default function LineChart({ .tickPadding(15) .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[1]); + let yTickValues = d3.range( + yAxis.min, + yAxis.max + 0.000001, + (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), + ); + + if (yTickValues[0] < 0) { + yTickValues = yTickValues.slice(1); + } const yAxisLabel = d3 .axisLeft(y) - .tickValues( - d3.range( - yAxis.min, - yAxis.max + 0.000001, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ), - ) + .tickValues(yTickValues) .tickSizeOuter(0) .tickSizeInner(0) .tickPadding(15) @@ -174,13 +178,7 @@ export default function LineChart({ if (gridLines) { const yAxisGrid = d3 .axisLeft(y) - .tickValues( - d3.range( - yAxis.min, - yAxis.max + 0.000001, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ), - ) + .tickValues(yTickValues) .tickSize(-width + marginLeft + marginRight - 20) .tickFormat(() => ""); @@ -323,6 +321,7 @@ export default function LineChart({ yAxis.min, yAxis.numDivisions, gridLines, + dataExists, ]); useEffect(() => { @@ -331,6 +330,7 @@ export default function LineChart({ useEffect(() => { setNewData(updateNewData()); + setDataExists(newData.length !== 0); }, [data, updateNewData]); return ( @@ -378,7 +378,7 @@ export default function LineChart({
)}

-

- {actualChange !== null && percentageChange} -

{yLabel}

diff --git a/src/components/Graphs/StackedBarChart/StackedBarChart.tsx b/src/components/Graphs/StackedBarChart/StackedBarChart.tsx index 0e742dc9..83d05435 100644 --- a/src/components/Graphs/StackedBarChart/StackedBarChart.tsx +++ b/src/components/Graphs/StackedBarChart/StackedBarChart.tsx @@ -152,22 +152,24 @@ export default function StackedBarChart({ ))} */} {/* */}
-
- {legend.map((l) => ( -
-
-
{l.text}
-
- ))} -
+ {data.length !== 0 && ( +
+ {legend.map((l) => ( +
+
+
{l.text}
+
+ ))} +
+ )}
); From a165b0959e657d3277ce3fda5b67c94b323e870d Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:24:52 -0500 Subject: [PATCH 07/26] account edit modal (#146) --- src/app/(management-portal)/layout.tsx | 1 + .../AccountEditModal/AccountEditModal.module.css | 10 ++++++++++ .../AccountEditModal/AccountEditModal.tsx | 14 +++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/app/(management-portal)/layout.tsx b/src/app/(management-portal)/layout.tsx index 8a9f3181..e80a04b7 100644 --- a/src/app/(management-portal)/layout.tsx +++ b/src/app/(management-portal)/layout.tsx @@ -56,6 +56,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
diff --git a/src/components/AccountEditModal/AccountEditModal.module.css b/src/components/AccountEditModal/AccountEditModal.module.css index e7523590..a9bbe608 100644 --- a/src/components/AccountEditModal/AccountEditModal.module.css +++ b/src/components/AccountEditModal/AccountEditModal.module.css @@ -204,3 +204,13 @@ justify-content: flex-end; gap: 15px; } + +.close { + width: fit-content; +} + +.closeButton { + cursor: pointer; + height: fit-content; + width: fit-content; +} diff --git a/src/components/AccountEditModal/AccountEditModal.tsx b/src/components/AccountEditModal/AccountEditModal.tsx index 09fd20fe..0a76aa34 100644 --- a/src/components/AccountEditModal/AccountEditModal.tsx +++ b/src/components/AccountEditModal/AccountEditModal.tsx @@ -3,6 +3,7 @@ import { CSSProperties, useState } from "react"; import { classes } from "@src/utils/utils"; import useAuth from "@src/hooks/useAuth"; +import XIcon from "@/src/app/icons/XIcon"; import styles from "./AccountEditModal.module.css"; import Profile from "./Profile"; import Password from "./Password"; @@ -13,12 +14,18 @@ const enum Page { } interface Props { + setShowModal: (args: boolean) => void; className?: string; style?: CSSProperties; setShowSuccessModal: (args: boolean) => void; } -const Modal = ({ className, style, setShowSuccessModal }: Props) => { +const Modal = ({ + setShowModal, + className, + style, + setShowSuccessModal, +}: Props) => { const [page, setPage] = useState(Page.PROFILE); const { logout } = useAuth(); const router = useRouter(); @@ -58,6 +65,11 @@ const Modal = ({ className, style, setShowSuccessModal }: Props) => { )}
+
+
setShowModal(false)}> + +
+
); }; From b17b6d2cd521152725dc27ddfdbe2d7b77a04d02 Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:31:07 -0500 Subject: [PATCH 08/26] flip arrows (#147) --- src/components/DataGrid/DataGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DataGrid/DataGrid.tsx b/src/components/DataGrid/DataGrid.tsx index 815138c5..8dc36c37 100644 --- a/src/components/DataGrid/DataGrid.tsx +++ b/src/components/DataGrid/DataGrid.tsx @@ -39,7 +39,7 @@ function SortButton({ !active && styles.SortButtonInactive, )} onClick={handleClick} - icon={active && !sortField.ascending ? faCaretDown : faCaretUp} + icon={active && !sortField.ascending ? faCaretUp : faCaretDown} /> ); } From e993e0279dd31ee2e8a153dfd0739ff10d3a654c Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:32:55 -0500 Subject: [PATCH 09/26] change error message for login (#148) --- src/app/auth/login/page.tsx | 12 +++++------- src/components/InputField/InputField.tsx | 5 ++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index b191f5e1..b4d563ce 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -27,6 +27,7 @@ export default function Page() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [emailError, setEmailError] = useState(""); + const [emailErrorOutline, setEmailErrorOutline] = useState(false); const [passwordError, setPasswordError] = useState(""); const [keepLogged, setKeepLogged] = useState(false); const [showGeneralError, setShowGeneralError] = useState(false); @@ -37,6 +38,7 @@ export default function Page() { const resetErrors = () => { setEmailError(""); + setEmailErrorOutline(false); setPasswordError(""); setShowGeneralError(false); }; @@ -64,14 +66,9 @@ export default function Page() { switch (error.code) { case "auth/invalid-email": case "auth/user-not-found": - setEmailError( - "Email address not found. Please try again or contact bei2023@gmail.com to retrieve it.", - ); - break; case "auth/wrong-password": - setPasswordError( - "Wrong password. Please try again or click Forgot Password to reset it.", - ); + setEmailErrorOutline(true); + setPasswordError("Invalid credentials. Please try again."); break; default: setShowGeneralError(true); @@ -164,6 +161,7 @@ export default function Page() { setShowGeneralError(false); }} showError={emailError !== ""} + errorOutline={emailErrorOutline} error={emailError} />
diff --git a/src/components/InputField/InputField.tsx b/src/components/InputField/InputField.tsx index 02d986e4..9de11722 100644 --- a/src/components/InputField/InputField.tsx +++ b/src/components/InputField/InputField.tsx @@ -17,6 +17,7 @@ type InputFieldProps = { value: string; onChange: (e: React.ChangeEvent) => void; showError?: boolean; + errorOutline?: boolean; error?: string; }; @@ -46,7 +47,9 @@ const InputField = (InputFieldProps: InputFieldProps) => { Date: Mon, 4 Nov 2024 17:27:56 -0500 Subject: [PATCH 10/26] Nathan/144 Fullstack Sprint Day Tasks 1 (#151) * added netlify logo at bottom of searches * edit chapter info now updates page immediately * changed some wording * fixed invalid calendar inputs on filters * parameter checks for changing year founded * refactored pagination * format * fix netlify and add calendar validation for patient search --------- Co-authored-by: Johannes Qian --- public/assets/logo-netlify-large.png | Bin 0 -> 10562 bytes .../chapter/[name]/page.tsx | 1 + .../chapter/search/page.module.css | 11 ++++- .../chapter/search/page.tsx | 4 ++ .../patient/search/page.module.css | 11 ++++- .../patient/search/page.tsx | 4 ++ .../volunteer/approval/page.module.css | 11 ++++- .../volunteer/approval/page.tsx | 4 ++ .../volunteer/search/page.module.css | 11 ++++- .../volunteer/search/page.tsx | 4 ++ src/components/AccountEditModal/Password.tsx | 2 +- .../ChapterGrid/ChapterGrid.module.css | 7 +++ src/components/ChapterGrid/ChapterGrid.tsx | 2 +- src/components/ChapterInfo/ChapterInfo.tsx | 2 + .../EditChapterModal/EditChapterModal.tsx | 15 ++++++- .../NetlifyLogo/NetlifyLogo.module.css | 32 ++++++++++++++ src/components/NetlifyLogo/NetlifyLogo.tsx | 14 ++++++ .../Pagination/Pagination.module.css | 8 ++++ src/components/Pagination/Pagination.tsx | 40 +++++++++++++----- .../Search/AdvancedSearch/CalendarInput.tsx | 6 ++- .../VolunteerAdvancedSearch/CalendarInput.tsx | 6 ++- 21 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 public/assets/logo-netlify-large.png create mode 100644 src/components/NetlifyLogo/NetlifyLogo.module.css create mode 100644 src/components/NetlifyLogo/NetlifyLogo.tsx diff --git a/public/assets/logo-netlify-large.png b/public/assets/logo-netlify-large.png new file mode 100644 index 0000000000000000000000000000000000000000..a935c675c3a44a849127e72f9c611930dfbfb133 GIT binary patch literal 10562 zcmeHtXH?V6)9@yArC1P9nqIF;k)l$A5D*bTsY;PSa>Fm=`rp3ri_h=ur9c2g4zd4HMwY)Xu|WbY4Nrm6PZHCx!Re-F zq6fgMr0tB$TmZ=Q8=cm>coQ{0;uvzVze;JT&Ui)dR$Q2$oC2?h-a8G{Hm9RCq*|^J zJNz(OfBQrG9h0rR$yyhE^!&D4dc67CqMduK+bMMM&k2{eu{wq8h9zWQ{y%{D0jrnm zS%_-CgDn2dx04YpWvg3C*83Aak9O7g;3NSc*%_nw-^#@!fu|r*w$5lEhA(L$j3;}J zOZVJK==retvu2?a4Tt3OApq#hVt;~d``Mp(G%x!r2f%U20-pW(zeE3nq*1F?hq_T)&8ukd=cEm(bfLQCkj5B5Hld=RFh$z}K zvUju9ia#3Q^`C?q5pi`oR2t^?#w<0fcjk{(~eOjYnVHoNA)H_|McQ z0N=;UW}yBc$gyIZ4Bm$qZ2KcHYNz&ZY*yT}p*fVX+*V#F))Nz@cCuNhJO`KPuUTC9 z#d`Z8Aanm0jSi5iU&z{j2Qq*B>N<(WpQ5UA(k2kbk~f@M9)!l=lur46oVNZ2w;sdmMJ93C@d;M){@ygeLh#HzLl>DF(%6A}wbweq^lAJgZ zzA}Sbh}d~=Yr?lH>$Gupo7|UHqzF553nC%e?E*`tr|_)72v)=q>oMy5P%lK?2f5zZ zg$Cuq8@^--t9+$=E~|E20s%W`Xk*84xGJnvxR|XiIO-i5x|N)bld{>QYOJ_x89qEX zNj{S}Z~I8@bHBfz@^*X{M6U1#@ued1b<;#+I1`lPpHM5*kGQ}B{L z`R7Junc}*7B2sk$yN+`Zrfd3~S*?z&pp*U1@q)D!fKP(t_#I0Apz)lsp|<@;pqs%r zmBG*7r!dC(U8Y$&^QE)72koi-jHwqoWzw^m^i-`L>57P@xlmNMemY9JCqvLY7W7o? z$kCMC4>gNvMZWgX3?p-2T)eX_aXo0W?#!o=h+)Mq^3444RH1-Gv!sKIYe5QL5H$Tb>mHcWbs#mNA9Uk4qC7a)yJ6 z62Ku^5GXz;WI{ow4P=}T`OKIk&%VVe)ekzM%3V2uLmJ!z+vhz^4djf|I{aJHs3+|r zp!8h`+}-K4Ug6(x=@r^6P?W=gm+$3++R*8-OfIG`asv4SV9XBkL8@Xl(4={Q{6R1_ z$Zn^|vQTyhfr~7=VWkqz(q9(p-i89_4srsZcvYpYmw)#tAFw+BTwbz4?wvn8F5#gB z+!Fx)IotlE1FILM@gjV{mJeFll8$)thv@P}>6~CRus#E=wej4Z&C8%0g97r0Aj;8e zROuWoIL5Z*j3OHZD+DmF_IBSn%=s;Xb@a20LfNEJTPUjhx6b8j-X=0;JIxBC-JCg# z8$Mifa3+PK@un1zqk3+h^F=&jgR5WN53F-Ke-IxXlzMnLlqg^uD85q*rRx?1x(gvn z+7Q|4p=Fh1zqu%4hIZjnJCdB4{64(rH5_|-KI){V#?4*j1(gU#&^e$xYsy+9Fp~j@ zOa9@S9D?c=u!qWBI!*0k7%*K$BsHcf<4)GSju%9c{uv-~S6B(1Lx4Pi{wl0?`y(HN zj@FyZW5oN>(H{J|DJz|}p~1`9zKoyNVC}I21ncngQ|?IqP!bk={nnAu2;Du)SNXkM z-)YeIw^}e)RVIUk&smRrqX*6{Dlr$iY@yEsq}N{#=sG^t`n)U|kVrPHag9%WKp;r& zKC=@X+67UThsjoJ71z7PE9?u_-3_=-a6pHn_AvjTTg5I{l(`1m6BYH3@PUe4eq&Bs zRd#!JjsRTr;181L{6bI_U0Sv(T;-r{HdGj3Te4~GhSDt_tHt7T*$V(&6q`d}4%kxx z^d{^-F1ADYzlU5-0I}kd57!v$i=8#qiC1vsx_$r{ad6YGhlYlG>5y67xPasPyr&8W z6VJ8p9Iw42&&0_h)hubHP2cl~14H7NJwq%Z(J2yP#^(f^_#<*xju09hv5CYk*|Zw< zF4oOqM$qud6Qh6rR62M#G@WPp1#A2;?^D%9LPnJF%dkf)6bkjt8x@)vwCvwkYI+{Y zt+dd|$_dfYmq`2ee(jo<7xI>~QRv zecq+SATLR&q|Oi5rY6?B2J0=JDVYiiW^rbUn=C}xiy}gK=Yo0DdjD290L-B+G#V}A z{NwjbelvR%b#h>Eus@_ST#il>9agtIC78$JuY0|{m!V_dzR|nQ zscpt3una@{K5H8&PaCyQR~8xl9pKe(oIfm7C^&@UnLWz?!nc;#Ysd0;Gw5ldk7QDc zF#KYZYqa05h|N>tkRsd7PHzvCZh9+Hml6Y*({X$6%;K;mOTjlr^znP57Wq)NTBaya zTGo8YV^AwyoEfyZKu+I(h*srVMQql4Nz_3$iuVsb(CP56;A@#l{~M<=->{r`IXW_r zM*Op&HeC3q!S0YsM}~S@|EN{d&w^gv2nmOGRmAL>{#U*>R7~|vpT4dhTYd43g}KtD z@Wo0F?!Nm_SxZ;n`Uh-Cr+bA}_{&b+LzX*mM}5q>ZJ()eomq?qt;n9|WbX3c`x#Mo z%d_8PCP!@bH3?={QyiRaPoRN|Rhq|4tiG+bQ%VoB!CA0w2{*L=4%F6{Q3A-xzRrbD zAyXWOFS~F8l5ysqgxEvg+ubqJpC=YBm;UI%luA6NJw@|71!~(>d}>VC?6V6|CCTxz ztuNghWg;I@T*vm){8y3?YNmEUJqpBJ8L38n>CdIkdgAx&2B5^l?tA)y+g_KH)p}pg zb}3BJu8X3Zd?Vo znJSj!`YrnhqTT10&O;h}|4JJkYWulwjKu6cl@3w$hooJ>a9?}1;{2_gC~uqYEd^!^ znqwmOaabc;ME)^j2s0(px~5z3-fN5a*xz{IJixOC_Re{&GKV^MwD<#?-v9_ZU_h^4 zR6iJp$h%i_NDvunfRd8y!y#sMun6%JfW%0rX+Ky^Z5r8D?!Wk{^am0bFem5@DXJHr zQN!P$Njt;?KuMtIvGyNM&ZMNnLu2w8zA2_vE$PF%M<#&z>O`fcLONlCcN~I8(X%mL&;dqP^b!>Du4J4HEA?Poh-AT29?-q} zmJ3(aI<8g+%2}&v8vVkiXnxDN{^vJCLmBj1OKqzC;^^h^waOfS&=0wyzYuiW``&VxUnnp|5@kSnme7~Gk#6uuoPvR)Q)B{Wp4zPplTlWP)L6{b3i%CQZW8QSO{hw8V#Uf6{f3fVUy(OKud;6u{} zp}*f4Pon`Pw%M0MnJN9m?F4@G?4ztNzN||IA6d-wuND(MJxWBWObLT3e_@*wI!vwA zP7G6gDkI9-5Yg(nplMh^UbLw6I7U;#-XArt5I$C7$$H7!);ZG_uwFFPLb=w!3hvcB zR_aUfS!W!R@`Wyi?eyNMPM&q%SmyoE+_96))o93!q&m(ltLFL6EE1&0!(4kTXpP^v z%NrU*x3+AWh_;!nAD=E{#ZsFFRCtPoP+;x!1=2)xbdzn#z`&44FiVTj@-4)&-MJ6B zpWdTH7y@>U2Ry8i@X%Z{|BIq4NwXVS@7}zb%R`!-rN)1HX=E=JwCZ$4jeN8r7;Gii zmeRwIm>p(n>Rkad{TWJE_`YxVDvpZEh;*Lr+r0mCpHzQi%+zCPLPkj5cD5BY*6}<& z({cHvbh6v0M9(3yeC}v_Y|PuhANF z^(&KEoOGD87Pr-U$*m_PgNt{ZVhbf!%kscJ@xDYhQ;*8}dpRNB;MnNxpnZ6=d^d~_ zlC3aF=hTL(#hRZM+M>4Nh>Gvm!z%u)cfagwSiy6A`O4NQPY!Gb~d zyjjxcYbzR|!Az52<>7$-5yRkN7V+jNDeV~N4w_k~v8S10SY7Lz)4|Kg^)=EJ>c(_M znRVFi^|gWgj9%#L6ApC-Zkhrce3Y)rrLd+R|;+X=v8U+RvrwVQeV#kMCND$=|2>Y-%%J_(Fn$sJ^{ z#UQUr;UU1IVEwE?NwDpeu}Q36qNz=esJwu834`dp6Fs4ncTTff{pYGvw!T1joa+;# z*#42`8_;j%E7nZzWwi}LS0ZgK8autlTd&9rEjAaISj~6qd6m(dtu?={ zk*zCWUTYeQ$`W*yG>la<<7r9E*XqTq!qk#Z>3HtxSMBN0j}(jzKUnw}c7ob8>yzX< zHom-U3Lcl%Erfhau@dDK%F}n`UWJS=yH;vJFI(N}*Q(`Y=x1l*|tz}x7RGN55 zvdu$0YF2GwWFTQ-5D`GG&cmZJUQ>Y|>U@v8DoVFvvGIh*V4GUYOm)xx>#EB^^OyZ$jxURFUHe9Q0+uB8)YDjPiK8RZ4?mlMn) zf!IUlw1t}^=wxI*sX^WNSc93yxQnq2W!WNmF35pT$Vim}7ahw~e7&hBfQD*(;+G;U zjvt8B&$WAoi|eNLLdNoi(>u+wC3Ln<7GkJ(CN<-dYpFAzGIc~XgiD<~SdkaJLw*L8 zUv`t9hx?BUs@jDW1g5I`(}zO|J$mj<|M81OL~Dhg#EDGG-(xX|x0DX3H0{P>Kg3sT_MoQdNPaD2vOQr_*#oX!(!3Y8o zjQFR)Myal;lHrbEP2{?J3nGj-7+Gm=PAvIm`^vE{ zAc?-gt@~hs+&plDHhX*7sF$x>)Y0d`%@&zLf2@=gUdlZNWqLR%(D9++P}+DvXlRX1 zy+BUOI^s%mmg-(2COpP2et4dC`#9$gna{3OJ#@KksmBFDltqGR{a)^;SHsyca{|MZ0tCObH*oL$_rj;gk|NGw-g9uZh)*aS6$0jR4{)8M(mTnh%lYcsB^0_ z7K5p)(RlX3Zm1Wa z#nE~;psByun&6X|o|1 z><=XB5JrG8I&Dd}&Po%} zQW|Fm&GA>Nm-~fNXVfmq+66UyX#u2%=NHO?-mh~Wl{cVNLpMAtf=tbB4m|KVe|0}U za@C40h?$GbY!u)~wG65daM2;|wJGnhSoWn!XRt4wBwZ`@#O$DB{K^JVVm94YTg2}; zKVv9jPZ6LT)TvBTj2jYGS85W-X;{a#AY%_1#nNzfp=V zHQ5EquW!J-yXfN!Q4;e*bxUgat(x@Q+vDzkea9_%rPsAP6D;aK?+~Iry*T-X-yv5S zp?Zp}iudrur|*ukFo@88mad9-s$E`HN%WOaIJ{Q#Vu0J6=!2zGa5f3eAA6b)2q0b; zt*93q)Axe+#$T!i`qRsN$sY3W zhUn(lm=PcatF(Y4$!(i)y_;n-H^fPX954hX-+R$%o3c81qhhmldiVUMii?ruCqpc` zuBerMywDhQS4(sT1|7kC7DjSuJT69sC7byw#Ga7vwG~enklUY1*mv09S2XO@D!=2V zzPKJ8O0OFDx#`>792ve3a2d%)71}6<}BLKIy}mXF9&G%j6Akmv1y<%knBB z=X=Fus`eIL;oWau{P;qRqa0!C00-%F(5snDtmRl}4+{%1cf<6Cd2w>04YwL*I0`1b zJl7iu?pwks{*s=Hjk7}VsFm>;5XM8s>IsPx_q&uLqK*Rx^Ar5acGa~7P2Qny4J;;4&+A-4Bw7t( z%X@xbI$xfBVh6f0(d7pL#A%6VZ z=NYWD{D#{RRDs?W`WX+RnY>8+%g$Ss^i9r}7|boYKEY88%*uUj^@~jKSfoF>8m^L! zOF5i;$#io=({Ko)NrDNN)*_qj1utdaHdvCCpT8&fZ5Y+$@tjH45-Q4A7wLIdIQb=_ zv1w+Eha(+4DTYVj?~>%3wcoPIe`3sWPtC|4nNV;2t6|oyROEcVy;gpBDDgp}YAY_1 zqe@i4Qzy%N2VrK<3BoDRQGwMqzFzgtjDxlx1#}gj)4YO?PSFZwN%;ra={=YXSAUp) z#3{)%G4CKXFeiTX^=;KYYWYyL7>h>~UT5z2s(qj@hc!#Lzrn!_pp6ax!h(;wy`ZHk zJv?aW4mN&~xM&9aJVi!4q_$dqzS^8%F+Z6SK%b#={jhcG61G-@0A=E3EyO;7-_*4( zCLfgg(mEEcQnz*OYUMKR>+x-q1xwk;Y)1rRrHEeeay2(l_H$+QnNI|Mvet`xnwoORxZ50%6Jm2wShm0z_TqlL%(B;gP6fA58!VhV^%~P@ z9c@T;s}m>Dw^*C;Suggkl%#CBhnm*0TQH`t2tQn6jT!xCHh{c_? zArRNouUO-y%n(xiutOJLBPsFm&)cU;UfB51H-mM&k7RO@FdlJ}s{6Rz;YA%D9%GVh z=VJ~^FIMMS@{iKd~N+}o9SLRV`r;P^qb1wv= zZtmx-Z5+-hP=3~jKq;P|?b71~#VPeqqjt4ifue&c>ajoXg48dSUD(KVD}Kj{J*4If zE}8suwHNG{$LNr0n{r-Mnw7Usu_9|Vfz0nqM#3jb#=Aa3?mv(cwKBQ=6yRd=g)>&( zMLVVaoEVFXWnIU@I$r|KND&ipInq9{c7H+)@+JW0=}X7QT*?Gd&3@L5q0d3{aY!Il zfbv6yBNLD?Fq{)~DbMH=Vu%lb}E>BmK&Ds;ZuwlH>=B)D4;%_rn6|HK2{0!_vq4_U$#i9m%csig% zu`TFTKoR4A)T2HJ^>F!PWQj+qQ;oFY3u}>F0%J`9?U4)aY24+)kb>)5q3R^jR||Ti zfkGs2k~s1GeCJix+JKwE$)BXK`sLNiq>=M%zF;_I{roS19n*c6W`7**`*@`cNgy*y z-&-LY9#X5TOKpGXx|~hBjMt==ZYM2aZ55%q$*C5J5Qfk`3hQw~+Pp7pV$;4pZB>hP zEkiIu^oGR4yf^7{Lbon5b6H`k>SYyxQs?P4TBF+!ryqw&-|rJaNdrd*`-+8qys1%D zOVqx`P4!QCkrm0gi-^}9ylWXv$)4l0O3X74YSLP8#SdpmiHKau)jQ2NRvs%%TO)kf z%uIk{;ij8NJ7)djSwDYSlJqN8?94=+Gcm*}fA^}Uq9<4@!B{5)jq?dU#=? zh2G>}ncI9|-c~P!0;Wo!zOG_303M2(-l75=3%38zb4jQmj1 znp9G9@SYA>?2QI4{uw^tT+Lp<-{$e1RrI7!4!CIkswh#aS^BolK2BpsDnr}#ry=LYXWC0$qw+%MY%UG2Nmy2km4yn&JLGlhX1~{k~8}i`6)9< zV}%>4zugAa6XdvbjuGW1eeD=}LVbh-ht?!WvNu+|%H%I$krnp70gt{y-4+Q(pB< zd}@KP@v2s3tf4I-1(?@?=}6D<(ae#oxgO_~OU!X2P{QU!*Ll)HX`NDLY`Ux=DCml+ zJOw*J&}DdfXm86=WwVhJd>GN+g0>BGhg|*rmVU=uv)RB~e=S04(C)CV3G3gTc~tTk zmh#S3x1(A}sAlRIvG4TIW=VJj+z0oxKBNg?4#3iRwuICu1NU{dMOeIl8(_^YZL(ja zh)Zw)<^6U@u6;G=PAi~w#s9!MZ=D=+m~6su-V}p(r&FfOFxA?vfC449uFJSmJ`{8c zqebyZ+cHc_0n8!bGHGqWfM|BeI>E}e4JybKCir0)6vU}@s7GBTW7qBidnicW`erK@=)SRRd~Z+OjLyY%E1|&?(`6xGUK;ilX)qWfrr;=Dft4 zoP+{7@VSTaCZSM~k)DU;DHpUP9iqOZ;i%Zqld&i~H3V7j#BgdjnDGAL`e-Gd9y60o z-G&?u2-|6IuT;6w5@vbQUpCTyQxtBN!3%x7Mb#^Dn@X7l@t2sw?W{`|#SPh|`W%_K zBZ|dI-Uxf&@$q}P9CW4Qk)W(=umHJafd)E-Wvz&NP=GufuH7-;8)2RukNmUCVC)np zXiWVf4L2NG;msqEen1M_36`XV5oXmb8sl=|ic_)fzUNKZz9TFQ#9#G07_(syw*UL` zqAwNTon=^CUb9Qnv}c1f!%CN{K`UBAMtLruFdiJX;3FIjM0|j4-0!;WM!a1sSDu>9_7QCJn|}tl$CHWL;k#rbrHbIYi!xTwlpa(7FCzdr oDmEFLrp2V$pic1h7Kkd+ng@hmMN0GqD!1Q$KDVIC{2M`DrasU7T literal 0 HcmV?d00001 diff --git a/src/app/(management-portal)/chapter/[name]/page.tsx b/src/app/(management-portal)/chapter/[name]/page.tsx index 0af920ac..2fa55b3a 100644 --- a/src/app/(management-portal)/chapter/[name]/page.tsx +++ b/src/app/(management-portal)/chapter/[name]/page.tsx @@ -143,6 +143,7 @@ export default function Page({ params }: { params: { name: string } }) { chapter={chapter} chapterPresident={chapterPresident} refreshUsers={fetchUsers} + refreshInfo={fetchChapterAndPresident} />
diff --git a/src/app/(management-portal)/chapter/search/page.module.css b/src/app/(management-portal)/chapter/search/page.module.css index 71af38a3..1fdc5dfa 100644 --- a/src/app/(management-portal)/chapter/search/page.module.css +++ b/src/app/(management-portal)/chapter/search/page.module.css @@ -6,6 +6,7 @@ flex-direction: column; align-items: center; row-gap: 30px; + position: relative; } .search-container { @@ -41,14 +42,14 @@ max-height: 0px; z-index: 1; opacity: 1; - overflow: hidden; + overflow: visible; } .table-container-show { max-height: 10000px; z-index: 1; opacity: 1; - overflow: auto; + overflow: visible; } .arrowButton { @@ -63,3 +64,9 @@ width: 100%; height: 100%; } + +.netlify { + position: absolute; + bottom: 20px; + right: 20px; +} diff --git a/src/app/(management-portal)/chapter/search/page.tsx b/src/app/(management-portal)/chapter/search/page.tsx index 5d56299c..7d335dfc 100644 --- a/src/app/(management-portal)/chapter/search/page.tsx +++ b/src/app/(management-portal)/chapter/search/page.tsx @@ -18,6 +18,7 @@ import firebaseInit from "@src/firebase/config"; import { RootState } from "@src/redux/rootReducer"; import { internalRequest } from "@src/utils/requests"; +import NetlifyLogo from "@src/components/NetlifyLogo/NetlifyLogo"; import styles from "./page.module.css"; firebaseInit(); @@ -93,6 +94,9 @@ export default function Page() { currentPage={currentPage} />
+
+ +
); } diff --git a/src/app/(management-portal)/patient/search/page.module.css b/src/app/(management-portal)/patient/search/page.module.css index 71af38a3..0b32ff3c 100644 --- a/src/app/(management-portal)/patient/search/page.module.css +++ b/src/app/(management-portal)/patient/search/page.module.css @@ -6,6 +6,7 @@ flex-direction: column; align-items: center; row-gap: 30px; + position: relative; } .search-container { @@ -41,14 +42,14 @@ max-height: 0px; z-index: 1; opacity: 1; - overflow: hidden; + overflow: visible; } .table-container-show { max-height: 10000px; z-index: 1; opacity: 1; - overflow: auto; + overflow: visible; } .arrowButton { @@ -63,3 +64,9 @@ width: 100%; height: 100%; } + +.netlify { + position: absolute; + bottom: 20px; + right: 40px; +} diff --git a/src/app/(management-portal)/patient/search/page.tsx b/src/app/(management-portal)/patient/search/page.tsx index 8193894c..959f5734 100644 --- a/src/app/(management-portal)/patient/search/page.tsx +++ b/src/app/(management-portal)/patient/search/page.tsx @@ -19,6 +19,7 @@ import Modal from "@src/components/Modal/Modal"; import firebaseInit from "@src/firebase/config"; import { RootState } from "@src/redux/rootReducer"; +import NetlifyLogo from "@src/components/NetlifyLogo/NetlifyLogo"; import styles from "./page.module.css"; firebaseInit(); @@ -141,6 +142,9 @@ export default function Page() { currentPage={currentPage} />
+
+ +
); } diff --git a/src/app/(management-portal)/volunteer/approval/page.module.css b/src/app/(management-portal)/volunteer/approval/page.module.css index 71af38a3..8cf8162c 100644 --- a/src/app/(management-portal)/volunteer/approval/page.module.css +++ b/src/app/(management-portal)/volunteer/approval/page.module.css @@ -6,6 +6,7 @@ flex-direction: column; align-items: center; row-gap: 30px; + position: relative; } .search-container { @@ -41,14 +42,14 @@ max-height: 0px; z-index: 1; opacity: 1; - overflow: hidden; + overflow: visible; } .table-container-show { max-height: 10000px; z-index: 1; opacity: 1; - overflow: auto; + overflow: visible; } .arrowButton { @@ -63,3 +64,9 @@ width: 100%; height: 100%; } + +.netlify { + position: absolute; + right: 5%; + bottom: 5%; +} diff --git a/src/app/(management-portal)/volunteer/approval/page.tsx b/src/app/(management-portal)/volunteer/approval/page.tsx index 1bf8f837..d5b07317 100644 --- a/src/app/(management-portal)/volunteer/approval/page.tsx +++ b/src/app/(management-portal)/volunteer/approval/page.tsx @@ -20,6 +20,7 @@ import { import firebaseInit from "@src/firebase/config"; import { RootState } from "@src/redux/rootReducer"; +import NetlifyLogo from "@src/components/NetlifyLogo/NetlifyLogo"; import styles from "./page.module.css"; firebaseInit(); @@ -145,6 +146,9 @@ export default function Page() { refreshUsers={fetchUsers} />
+
+ +
); } diff --git a/src/app/(management-portal)/volunteer/search/page.module.css b/src/app/(management-portal)/volunteer/search/page.module.css index 430c4a79..24892cb5 100644 --- a/src/app/(management-portal)/volunteer/search/page.module.css +++ b/src/app/(management-portal)/volunteer/search/page.module.css @@ -6,6 +6,7 @@ flex-direction: column; align-items: center; row-gap: 30px; + position: relative; } .search-container { @@ -41,14 +42,14 @@ max-height: 0px; z-index: 1; opacity: 1; - overflow: hidden; + overflow: visible; } .table-container-show { max-height: 10000px; z-index: 1; opacity: 1; - overflow: auto; + overflow: visible; } .arrowButton { @@ -68,3 +69,9 @@ width: 35%; height: 23%; } + +.netlify { + position: absolute; + bottom: 20px; + right: 40px; +} diff --git a/src/app/(management-portal)/volunteer/search/page.tsx b/src/app/(management-portal)/volunteer/search/page.tsx index e9c3103c..da159fdb 100644 --- a/src/app/(management-portal)/volunteer/search/page.tsx +++ b/src/app/(management-portal)/volunteer/search/page.tsx @@ -18,6 +18,7 @@ import { import firebaseInit from "@src/firebase/config"; import { RootState } from "@src/redux/rootReducer"; +import NetlifyLogo from "@src/components/NetlifyLogo/NetlifyLogo"; import styles from "./page.module.css"; firebaseInit(); @@ -139,6 +140,9 @@ export default function Page() { refreshUsers={fetchUsers} />
+
+ +
); } diff --git a/src/components/AccountEditModal/Password.tsx b/src/components/AccountEditModal/Password.tsx index 0c142724..febb5106 100644 --- a/src/components/AccountEditModal/Password.tsx +++ b/src/components/AccountEditModal/Password.tsx @@ -128,7 +128,7 @@ export default function Password({ setShowSuccessModal }: Props) { onClick={reset} className={`${styles.submitButton} ${styles.disabled}`} > - Cancel + Clear + <> + {Number.isNaN(page) ? ( +
+ ) : ( + + )} + ); })}
diff --git a/src/components/Search/AdvancedSearch/CalendarInput.tsx b/src/components/Search/AdvancedSearch/CalendarInput.tsx index 5a8e363c..e0955ccf 100644 --- a/src/components/Search/AdvancedSearch/CalendarInput.tsx +++ b/src/components/Search/AdvancedSearch/CalendarInput.tsx @@ -50,7 +50,11 @@ export default function Calendar({ value, onChange }: CalendarInputProp) { }} onChange={(val) => { if (val === null) return; - onChange(transformDate(val)); + if (val.toString() === "Invalid Date") { + onChange(""); + } else { + onChange(transformDate(val)); + } }} />
diff --git a/src/components/VolunteerSearch/VolunteerAdvancedSearch/CalendarInput.tsx b/src/components/VolunteerSearch/VolunteerAdvancedSearch/CalendarInput.tsx index b8449e77..08813a6d 100644 --- a/src/components/VolunteerSearch/VolunteerAdvancedSearch/CalendarInput.tsx +++ b/src/components/VolunteerSearch/VolunteerAdvancedSearch/CalendarInput.tsx @@ -50,7 +50,11 @@ export default function Calendar({ value, onChange }: CalendarInputProp) { }} onChange={(val) => { if (val === null) return; - onChange(transformDate(val)); + if (val.toString() === "Invalid Date") { + onChange(""); + } else { + onChange(transformDate(val)); + } }} />
From d65af445d8ae6f0f20cef96eb36c00ac68c10a97 Mon Sep 17 00:00:00 2001 From: Johannes Qian Date: Mon, 4 Nov 2024 17:36:01 -0500 Subject: [PATCH 11/26] Fix overflow error on analytics screen --- .../MathScreen/MathScreen.module.css | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/Dashboard/MathScreen/MathScreen.module.css b/src/components/Dashboard/MathScreen/MathScreen.module.css index 23d4bd1b..2d9d1f4c 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.module.css +++ b/src/components/Dashboard/MathScreen/MathScreen.module.css @@ -37,36 +37,37 @@ } .textStatsWithHeader { - flex: 0.9; /* This can be adjusted based on your layout needs */ - display: flex; /* Default to flexbox */ - flex-direction: column; /* Stack children vertically */ - row-gap: 20px; /* Space between items */ + min-height: 0; /* Allows it to shrink if content reduces */ + display: flex; + flex-direction: column; + row-gap: 20px; + overflow: hidden; /* Prevents excess space */ } .body { - display: flex; /* Main content container */ - flex-direction: row; /* Align items vertically */ + display: flex; + flex-direction: row; + flex-wrap: wrap; /* Enables wrapping on smaller screens */ } @media (max-width: 1300px) { .textStats { - display: grid; /* Switch to grid layout */ - grid-template-columns: repeat(2, 1fr); /* Create 2 equal columns */ - gap: 10px; /* Space between grid items */ - width: 100%; /* Ensure it takes full width */ - margin-bottom: 20px; /* Optional: space below textStats */ + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + width: 100%; + margin-bottom: 20px; } .body { flex-direction: column-reverse; - margin-top: 20px; /* Optional: space between textStats and body */ + margin-top: 20px; } } .body > .box { - /* Assuming .box is the class for the custom component */ - flex: 0 0 calc(50% - 10px); /* Adjust width when needed */ - margin: 5px; /* Optional: add some spacing */ + flex: 1 1 calc(50% - 10px); /* Flexible sizing for adaptability */ + margin: 5px; } .graphs { From 33685f84cf47bd97374e58bfb033e0bb15226892 Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:33:42 -0500 Subject: [PATCH 12/26] change email error (#149) * change email error * add date check. still shows successful update though --- src/app/api/volunteer/route.ts | 6 +++++- src/components/AccountEditModal/Profile.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/api/volunteer/route.ts b/src/app/api/volunteer/route.ts index bb81e2e2..3112ecf7 100644 --- a/src/app/api/volunteer/route.ts +++ b/src/app/api/volunteer/route.ts @@ -62,7 +62,11 @@ export const PATCH = APIWrapper({ throw new Error("You do not have permission to acccess this user"); } - if (newFields.email !== null && email === newFields.email) { + if ( + newFields.email !== null && + newFields.email !== undefined && + email !== newFields.email + ) { await updateUserEmail(email, newFields.email); } diff --git a/src/components/AccountEditModal/Profile.tsx b/src/components/AccountEditModal/Profile.tsx index b23b2940..31af902c 100644 --- a/src/components/AccountEditModal/Profile.tsx +++ b/src/components/AccountEditModal/Profile.tsx @@ -65,7 +65,11 @@ export default function Profile({ setShowSuccessModal }: Props) { if (dateArray.length === 3) { const [month, day, year] = dateArray.map(Number); const updatedDate = new Date(year, month - 1, day); - if (!Number.isNaN(updatedDate.getTime())) { + if ( + !Number.isNaN(updatedDate.getTime()) && + updatedDate.getFullYear() > 1950 && + updatedDate < new Date() + ) { setUpdatedBirthDate(updatedDate); } } From 66c1c8a89b38a59a2898d15b61694fdbb42d30c5 Mon Sep 17 00:00:00 2001 From: nikvijay07 <113941701+nikvijay07@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:41:39 -0500 Subject: [PATCH 13/26] no data found (#153) * no data found * lint and prettier * adding padding and fixing no data found bug * Fix Percentage numbers, remove stack bar chart, fix some padding issues --------- Co-authored-by: Johannes Qian --- server/mongodb/actions/AggregatedAnalytics.ts | 4 +- .../Dashboard/MathScreen/MathScreen.tsx | 8 +- .../Dashboard/ReadingScreen/ReadingScreen.tsx | 22 +- .../Dashboard/WritingScreen/WritingScreen.tsx | 17 +- .../Graphs/BarChart/BarChart.module.scss | 44 +- src/components/Graphs/BarChart/BarChart.tsx | 452 ++++++++++-------- .../Graphs/LineChart/LineChart.module.scss | 46 +- src/components/Graphs/LineChart/LineChart.tsx | 366 +++++++------- 8 files changed, 544 insertions(+), 415 deletions(-) diff --git a/server/mongodb/actions/AggregatedAnalytics.ts b/server/mongodb/actions/AggregatedAnalytics.ts index d9e269f9..9e3d9465 100644 --- a/server/mongodb/actions/AggregatedAnalytics.ts +++ b/server/mongodb/actions/AggregatedAnalytics.ts @@ -292,6 +292,7 @@ export const getAggregatedAnalytics = async ( Object.entries({ overall: { streakLength: 0, + streakHistory: 0, }, math: { avgAccuracy: 0, @@ -395,7 +396,8 @@ export const getAggregatedAnalytics = async ( if (!obj.sessionCompletion) { obj.sessionCompletion = [dr]; } else { - obj.sessionCompletion.push(dr); + obj.sessionCompletion.unshift(dr); + // obj.sessionCompletion.push(dr); } } return; diff --git a/src/components/Dashboard/MathScreen/MathScreen.tsx b/src/components/Dashboard/MathScreen/MathScreen.tsx index 5b4816de..181e7de5 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.tsx +++ b/src/components/Dashboard/MathScreen/MathScreen.tsx @@ -66,7 +66,8 @@ const MathScreen = ({ info="Vidushi" data={accuracyData} fullWidth - yLabel="Questions" + yLabel="" + // yLabel="Questions" gridLines /> { return ( @@ -74,16 +68,16 @@ export default function ReadingScreen({
-
- v.value + v.value / 5)) ?? 1), - numDivisions: Math.min( - 5, - Math.floor(d3.max(data.map((v) => v.value)) ?? 1) + 1, + max: Math.max( + Math.ceil(d3.max(data.map((v) => v.value + v.value / 5)) ?? 1), + 1, ), - format: d3.format("d"), + numDivisions: 5, + // Math.min( + // 5, + // Math.floor(d3.max(data.map((v) => v.value)) ?? 1) + 1, + // ), + format: (d: d3.NumberValue) => { + const numberValue = Number(d); // Convert to a number + return Number.isInteger(numberValue) + ? numberValue.toString() + : d3.format(".1f")(numberValue); + }, + // format: d3.format("d"), }, hoverable = false, percentageChange = false, @@ -145,214 +155,250 @@ export default function BarChart({ setLargest(indexOfMax()); const svg = d3.select(windowRef.current); - svg.select(".x-axis-hor").remove(); - svg.select(".y-axis-vert").remove(); - svg.select(".x-axis-grid").remove(); - svg.select(".y-axis-grid").remove(); - svg.select(".x-axis-top").remove(); - svg.select(".x-axis-bottom").remove(); - svg.select(".y-axis").remove(); - svg.selectAll(".bar").remove(); - - const xAxisLabelTop = d3 - .axisBottom(x) - .ticks(newData.length) - .tickSizeOuter(0) - .tickSizeInner(0) - .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[0] ?? ""); - - const xAxisLabelBottom = d3 - .axisBottom(x) - .ticks(newData.length) - .tickSizeOuter(0) - .tickSizeInner(0) - .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[1] ?? ""); - - const yAxisLabel = d3 - .axisLeft(y) - .tickValues( - d3.range( - yAxis.min, - yAxis.max + 0.000001, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ), - ) - .tickSizeOuter(0) - .tickSizeInner(0) - .tickPadding(15) - .tickFormat(yAxisFormat); - - if (gridLines) { - const yAxisGrid = d3 - .axisLeft(y) - .tickValues( - d3.range( - yAxis.min, - yAxis.max + 0.000001, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ), - ) - .tickSize(-width + marginLeft + marginRight - 20) - .tickFormat(() => ""); - const axisVert = d3 + if (!dataExists) { + svg.selectAll("*").remove(); + + const iconSVG = ` + + + + `; + const iconSize = 30; + + svg + .append("foreignObject") + .attr("x", width / 2 - 97) + .attr("y", height / 2 - 37) // Position above the "No Data" text + .attr("width", iconSize) + .attr("height", iconSize) + .html(iconSVG); + + svg + .append("text") + .attr("x", width / 2) + .attr("y", height / 2 - 20) + .attr("text-anchor", "middle") + .attr("dominant-baseline", "middle") + .style("font-size", "16px") + .style("font-family", poppins500.style.fontFamily) + .style("fill", "#2B3674") + .text("No Data Found"); + } else { + svg.selectAll("*").remove(); + svg.select(".x-axis-hor").remove(); + svg.select(".y-axis-vert").remove(); + svg.select(".x-axis-grid").remove(); + svg.select(".y-axis-grid").remove(); + svg.select(".x-axis-top").remove(); + svg.select(".x-axis-bottom").remove(); + svg.select(".y-axis").remove(); + svg.selectAll(".bar").remove(); + + const xAxisLabelTop = d3 + .axisBottom(x) + .ticks(newData.length) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickPadding(15) + .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[0] ?? ""); + + const xAxisLabelBottom = d3 + .axisBottom(x) + .ticks(newData.length) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickPadding(15) + .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[1] ?? ""); + + const yAxisLabel = d3 .axisLeft(y) .tickValues( d3.range( yAxis.min, - yAxis.max, + yAxis.max + 0.000001, (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), ), ) - .tickSize(0) - .tickFormat(() => ""); - - const axisHor = d3 - .axisBottom( - d3.scaleLinear( - [0, newData.length - 1], - [marginLeft, width - marginRight + 20], - ), - ) - .ticks(newData.length - 1) .tickSizeOuter(0) .tickSizeInner(0) - .tickFormat(() => ""); - + .tickPadding(15) + .tickFormat(yAxisFormat); + + if (gridLines) { + const yAxisGrid = d3 + .axisLeft(y) + .tickValues( + d3.range( + yAxis.min, + yAxis.max + 0.000001, + (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), + ), + ) + .tickSize(-width + marginLeft + marginRight - 20) + .tickFormat(() => ""); + + const axisVert = d3 + .axisLeft(y) + .tickValues( + d3.range( + yAxis.min, + yAxis.max, + (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), + ), + ) + .tickSize(0) + .tickFormat(() => ""); + + const axisHor = d3 + .axisBottom( + d3.scaleLinear( + [0, newData.length - 1], + [marginLeft, width - marginRight + 20], + ), + ) + .ticks(newData.length - 1) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickFormat(() => ""); + + svg + .append("g") + .attr("class", `y-axis-vert`) + .attr("transform", `translate(${marginLeft - 5}, 0)`) + .call(axisVert); + + svg + .append("g") + .attr("class", `y-axis-grid ${styles.yAxis}`) + .attr("transform", `translate(${marginLeft - 5}, 0)`) + .call(yAxisGrid); + + svg + .append("g") + .attr("transform", `translate(-5, ${height - marginBottom})`) + .attr("class", "x-axis-hor") + .style("font", `10px ${poppins500.style.fontFamily}`) + .call(axisHor); + } svg .append("g") - .attr("class", `y-axis-vert`) - .attr("transform", `translate(${marginLeft - 5}, 0)`) - .call(axisVert); + .attr( + "transform", + `translate(${barWidth / 2}, ${height - marginBottom})`, + ) + .attr("class", "x-axis-top") + .style("font", `10px ${poppins500.style.fontFamily}`) + .style("color", "#343539") + .call(xAxisLabelTop) + .call((g) => g.select(".domain").remove()); svg .append("g") - .attr("class", `y-axis-grid ${styles.yAxis}`) - .attr("transform", `translate(${marginLeft - 5}, 0)`) - .call(yAxisGrid); + .attr( + "transform", + `translate(${barWidth / 2}, ${height - marginBottom + 15})`, + ) + .attr("class", "x-axis-bottom") + .style("font", `10px ${inter500.style.fontFamily}`) + .style("color", "#B0BBD5") + .call(xAxisLabelBottom) + .call((g) => g.select(".domain").remove()); svg .append("g") - .attr("transform", `translate(-5, ${height - marginBottom})`) - .attr("class", "x-axis-hor") - .style("font", `10px ${poppins500.style.fontFamily}`) - .call(axisHor); - } - - svg - .append("g") - .attr("transform", `translate(${barWidth / 2}, ${height - marginBottom})`) - .attr("class", "x-axis-top") - .style("font", `10px ${poppins500.style.fontFamily}`) - .style("color", "#343539") - .call(xAxisLabelTop) - .call((g) => g.select(".domain").remove()); - - svg - .append("g") - .attr( - "transform", - `translate(${barWidth / 2}, ${height - marginBottom + 15})`, - ) - .attr("class", "x-axis-bottom") - .style("font", `10px ${inter500.style.fontFamily}`) - .style("color", "#B0BBD5") - .call(xAxisLabelBottom) - .call((g) => g.select(".domain").remove()); - - svg - .append("g") - .attr("transform", `translate(${marginLeft}, 0)`) - .attr("class", "y-axis") - .style("font", `9.5px ${poppins400.style.fontFamily}`) - .style("color", "#A5A5A5") - .call(yAxisLabel) - .call((g) => g.select(".domain").remove()); - - const barsGroup = svg - .selectAll(".bar") - .data(newData) - .join("g") - .attr("class", "bar") - .attr("transform", (d, i) => `translate(${x(i)}, 0)`); // Position bars - - const tooltip = d3.select("#tooltip"); - - if (hoverable) { - barsGroup - .append("path") - .attr("d", (d) => { - const barHeight = height - marginBottom - y(d.value); - const radius = barWidth / 2; // Radius for the semi-circle top - const x0 = 0; - const y0 = y(d.value); - - // Define the path for a rectangle with a semi-circle top - if (d.value === 0) { - // Render a semi-circle (half-circle) if the value is 0 + .attr("transform", `translate(${marginLeft}, 0)`) + .attr("class", "y-axis") + .style("font", `9.5px ${poppins400.style.fontFamily}`) + .style("color", "#A5A5A5") + .call(yAxisLabel) + .call((g) => g.select(".domain").remove()); + + const barsGroup = svg + .selectAll(".bar") + .data(newData) + .join("g") + .attr("class", "bar") + .attr("transform", (d, i) => `translate(${x(i)}, 0)`); // Position bars + + const tooltip = d3.select("#tooltip"); + + if (hoverable) { + barsGroup + .append("path") + .attr("d", (d) => { + const barHeight = height - marginBottom - y(d.value); + const radius = barWidth / 2; // Radius for the semi-circle top + const x0 = 0; + const y0 = y(d.value); + + // Define the path for a rectangle with a semi-circle top + if (d.value === 0) { + // Render a semi-circle (half-circle) if the value is 0 + return ` + M ${x0},${y0} + A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} + `; + } + // Render a rectangle with a rounded top if the value is non-zero return ` - M ${x0},${y0} - A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} - `; - } - // Render a rectangle with a rounded top if the value is non-zero - return ` - M ${x0},${y0 + barHeight} - V ${y0 + radius} - A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} - V ${y0 + barHeight} - H ${x0} Z - `; - }) - .style("fill", (d, i) => - highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", - ) - .on("mouseover", (event: MouseEvent, d: DataRecord) => { - tooltip.transition().duration(0).style("opacity", 1); - tooltip - .html(`${d.value}`) - .style("left", `${event.pageX + 5}px`) - .style("top", `${event.pageY - 28}px`); - }) - .on("mousemove", (event: MouseEvent) => { - tooltip - .style("left", `${event.pageX + 5}px`) - .style("top", `${event.pageY - 28}px`); - }) - .on("mouseout", () => { - tooltip.transition().duration(0).style("opacity", 0); - }); - } else { - barsGroup - .append("path") - .attr("d", (d) => { - const barHeight = height - marginBottom - y(d.value); - const radius = barWidth / 2; // Radius for the semi-circle top - const x0 = 0; - const y0 = y(d.value); - - // Define the path for a rectangle with a semi-circle top - if (d.value === 0) { - // Render a semi-circle (half-circle) if the value is 0 + M ${x0},${y0 + barHeight} + V ${y0 + radius} + A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} + V ${y0 + barHeight} + H ${x0} Z + `; + }) + .style("fill", (d, i) => + highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", + ) + .on("mouseover", (event: MouseEvent, d: DataRecord) => { + tooltip.transition().duration(0).style("opacity", 1); + tooltip + .html( + `${Number.isInteger(d.value) ? d.value : d.value.toFixed(2)}`, + ) + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + }) + .on("mousemove", (event: MouseEvent) => { + tooltip + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + }) + .on("mouseout", () => { + tooltip.transition().duration(0).style("opacity", 0); + }); + } else { + barsGroup + .append("path") + .attr("d", (d) => { + const barHeight = height - marginBottom - y(d.value); + const radius = barWidth / 2; // Radius for the semi-circle top + const x0 = 0; + const y0 = y(d.value); + + // Define the path for a rectangle with a semi-circle top + if (d.value === 0) { + // Render a semi-circle (half-circle) if the value is 0 + return ` + M ${x0},${y0} + A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} + `; + } + // Render a rectangle with a rounded top if the value is non-zero return ` - M ${x0},${y0} - A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} - `; - } - // Render a rectangle with a rounded top if the value is non-zero - return ` - M ${x0},${y0 + barHeight} - V ${y0 + radius} - A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} - V ${y0 + barHeight} - H ${x0} Z - `; - }) - .style("fill", (d, i) => - highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", - ); + M ${x0},${y0 + barHeight} + V ${y0 + radius} + A ${radius},${radius} 0 0 1 ${x0 + barWidth},${y0 + radius} + V ${y0 + barHeight} + H ${x0} Z + `; + }) + .style("fill", (d, i) => + highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", + ); + } } return () => window.removeEventListener("scroll", onScroll); @@ -377,9 +423,12 @@ export default function BarChart({ useEffect(() => { setNewData(updateNewData()); - setDataExists(newData.length !== 0); }, [data, updateNewData]); + useEffect(() => { + setDataExists(newData.length !== 0); + }, [newData]); + return (
-
+

{title}

{info !== "" && (
)} +
+

{actualChange !== null && percentageChange && (actualChange < 0 - ? `⏷ \xa0 ${(actualChange * 100).toFixed(2)}%` - : `⏶ \xa0 ${(actualChange * 100).toFixed(2)}%`)} + ? `⏷ ${(actualChange * 100).toFixed(2)}%` + : `⏶ ${(actualChange * 100).toFixed(2)}%`)}

-
-

{yLabel}

-
+
+
+

{yLabel}

newData[d.valueOf()].interval.split(" ")[0]); - - const xAxisLabelBottom = d3 - .axisBottom(x) - .ticks(newData.length - 1) - .tickSizeOuter(0) - .tickSizeInner(0) - .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[1]); - - let yTickValues = d3.range( - yAxis.min, - yAxis.max + 0.000001, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ); - - if (yTickValues[0] < 0) { - yTickValues = yTickValues.slice(1); - } - const yAxisLabel = d3 - .axisLeft(y) - .tickValues(yTickValues) - .tickSizeOuter(0) - .tickSizeInner(0) - .tickPadding(15) - .tickFormat(yAxisFormat); - if (gridLines) { - const yAxisGrid = d3 - .axisLeft(y) - .tickValues(yTickValues) - .tickSize(-width + marginLeft + marginRight - 20) - .tickFormat(() => ""); - const axisVert = d3 - .axisLeft(y) - .tickValues( - d3.range( - yAxis.min, - yAxis.max, - (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), - ), - ) - .tickSize(0) - .tickFormat(() => ""); - - const axisHor = d3 - .axisBottom( - d3.scaleLinear( - [0, newData.length - 1], - [marginLeft, width - marginRight + 20], - ), - ) + if (!dataExists) { + svg.selectAll("*").remove(); + + const iconSVG = ` + + + + `; + const iconSize = 30; + + svg + .append("foreignObject") + .attr("x", width / 2 - 97) + .attr("y", height / 2 - 37) // Position above the "No Data" text + .attr("width", iconSize) + .attr("height", iconSize) + .html(iconSVG); + + svg + .append("text") + .attr("x", width / 2) + .attr("y", height / 2 - 20) + .attr("text-anchor", "middle") + .attr("dominant-baseline", "middle") + .style("font-size", "16px") + .style("font-family", poppins500.style.fontFamily) + .style("fill", "#2B3674") + .text("No Data Found"); + } else { + svg.select(".x-axis-hor").remove(); + svg.select(".y-axis-vert").remove(); + svg.select(".x-axis-grid").remove(); + svg.select(".y-axis-grid").remove(); + svg.select(".x-axis-top").remove(); + svg.select(".x-axis-bottom").remove(); + svg.select(".y-axis").remove(); + svg.select(".path").remove(); + svg.select(".point").remove(); + svg.selectAll("*").remove(); + + const xAxisLabelTop = d3 + .axisBottom(x) .ticks(newData.length - 1) .tickSizeOuter(0) .tickSizeInner(0) - .tickFormat(() => ""); + .tickPadding(15) + .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[0]); + const xAxisLabelBottom = d3 + .axisBottom(x) + .ticks(newData.length - 1) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickPadding(15) + .tickFormat((d) => newData[d.valueOf()].interval.split(" ")[1]); + + let yTickValues = d3.range( + yAxis.min, + yAxis.max + 0.000001, + (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), + ); + + if (yTickValues[0] < 0) { + yTickValues = yTickValues.slice(1); + } + const yAxisLabel = d3 + .axisLeft(y) + .tickValues(yTickValues) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickPadding(15) + .tickFormat(yAxisFormat); + if (gridLines) { + const yAxisGrid = d3 + .axisLeft(y) + .tickValues(yTickValues) + .tickSize(-width + marginLeft + marginRight - 20) + .tickFormat(() => ""); + + const axisVert = d3 + .axisLeft(y) + .tickValues( + d3.range( + yAxis.min, + yAxis.max, + (yAxis.max - yAxis.min) / (yAxis.numDivisions - 1), + ), + ) + .tickSize(0) + .tickFormat(() => ""); + + const axisHor = d3 + .axisBottom( + d3.scaleLinear( + [0, newData.length - 1], + [marginLeft, width - marginRight + 20], + ), + ) + .ticks(newData.length - 1) + .tickSizeOuter(0) + .tickSizeInner(0) + .tickFormat(() => ""); + + svg + .append("g") + .attr("class", `y-axis-vert`) + .attr("transform", `translate(${marginLeft - 5}, 0)`) + .call(axisVert); + + svg + .append("g") + .attr("class", `y-axis-grid ${styles.yAxis}`) + .attr("transform", `translate(${marginLeft - 5}, 0)`) + .call(yAxisGrid); + + svg + .append("g") + .attr("transform", `translate(-5, ${height - marginBottom})`) + .attr("class", "x-axis-hor") + .style("font", `10px ${poppins500.style.fontFamily}`) + .call(axisHor); + } svg .append("g") - .attr("class", `y-axis-vert`) - .attr("transform", `translate(${marginLeft - 5}, 0)`) - .call(axisVert); + .attr("transform", `translate(0, ${height - marginBottom})`) + .attr("class", "x-axis-top") + .style("font", `10px ${poppins500.style.fontFamily}`) + .style("color", "#343539") + .call(xAxisLabelTop) + .call((g) => g.select(".domain").remove()); svg .append("g") - .attr("class", `y-axis-grid ${styles.yAxis}`) - .attr("transform", `translate(${marginLeft - 5}, 0)`) - .call(yAxisGrid); + .attr("transform", `translate(0, ${height - marginBottom + 15})`) + .attr("class", "x-axis-bottom") + .style("font", `10px ${inter500.style.fontFamily}`) + .style("color", "#B0BBD5") + .call(xAxisLabelBottom) + .call((g) => g.select(".domain").remove()); svg .append("g") - .attr("transform", `translate(-5, ${height - marginBottom})`) - .attr("class", "x-axis-hor") - .style("font", `10px ${poppins500.style.fontFamily}`) - .call(axisHor); - } - svg - .append("g") - .attr("transform", `translate(0, ${height - marginBottom})`) - .attr("class", "x-axis-top") - .style("font", `10px ${poppins500.style.fontFamily}`) - .style("color", "#343539") - .call(xAxisLabelTop) - .call((g) => g.select(".domain").remove()); - - svg - .append("g") - .attr("transform", `translate(0, ${height - marginBottom + 15})`) - .attr("class", "x-axis-bottom") - .style("font", `10px ${inter500.style.fontFamily}`) - .style("color", "#B0BBD5") - .call(xAxisLabelBottom) - .call((g) => g.select(".domain").remove()); - - svg - .append("g") - .attr("transform", `translate(${marginLeft}, 0)`) - .attr("class", "y-axis") - .style("font", `9.5px ${poppins400.style.fontFamily}`) - .style("color", "#A5A5A5") - .call(yAxisLabel) - .call((g) => g.select(".domain").remove()); - - const d3line = line(newData.map((d, i) => [i, d.value])) as string; - - const tooltip = d3.select("#tooltip"); - // Draw the line connecting the points - svg - .append("path") - .datum(newData) // Bind data to the path - .attr("class", styles.linePath) // Add a class for styling - .attr("d", d3line) // Create the line using the defined line function - .style("fill", "none") // No fill for the line - .style("stroke", "#008AFC") // Line color - .style("stroke-width", 5) // Line width - .style("stroke-linecap", "round") - .style("stroke-linejoin", "round") - .style("filter", "url(#drop-shadow)"); - - // Draw points as circles on the line - if (hoverable) { + .attr("transform", `translate(${marginLeft}, 0)`) + .attr("class", "y-axis") + .style("font", `9.5px ${poppins400.style.fontFamily}`) + .style("color", "#A5A5A5") + .call(yAxisLabel) + .call((g) => g.select(".domain").remove()); + + const d3line = line(newData.map((d, i) => [i, d.value])) as string; + + const tooltip = d3.select("#tooltip"); + // Draw the line connecting the points + svg - .selectAll(".point") - .data(newData) - .join("circle") - .attr("class", styles.hoverCircle) - .attr("cx", (d, i) => x(i)) // X position based on index - .attr("cy", (d) => y(d.value)) // Y position based on the value - .attr("r", 7) // Radius of the circles - .style("fill", "white") // Set fill color to white - .style("stroke", "#008AFC") // Set stroke color - .style("stroke-width", 5) // Set stroke width - .style("opacity", 0) // Initially hidden - - .on( - "mouseover", - function handleMouseOver(event: MouseEvent, d: DataRecord) { - tooltip.transition().duration(0).style("opacity", 1); + .append("path") + .datum(newData) // Bind data to the path + .attr("class", styles.linePath) // Add a class for styling + .attr("d", d3line) // Create the line using the defined line function + .style("fill", "none") // No fill for the line + .style("stroke", "#008AFC") // Line color + .style("stroke-width", 5) // Line width + .style("stroke-linecap", "round") + .style("stroke-linejoin", "round") + .style("filter", "url(#drop-shadow)"); + + // Draw points as circles on the line + if (hoverable) { + svg + .selectAll(".point") + .data(newData) + .join("circle") + .attr("class", styles.hoverCircle) + .attr("cx", (d, i) => x(i)) // X position based on index + .attr("cy", (d) => y(d.value)) // Y position based on the value + .attr("r", 7) // Radius of the circles + .style("fill", "white") // Set fill color to white + .style("stroke", "#008AFC") // Set stroke color + .style("stroke-width", 5) // Set stroke width + .style("opacity", 0) // Initially hidden + + .on( + "mouseover", + function handleMouseOver(event: MouseEvent, d: DataRecord) { + tooltip.transition().duration(0).style("opacity", 1); + tooltip + .html( + `${Number.isInteger(d.value) ? d.value : d.value.toFixed(2)}`, + ) + .style("left", `${event.pageX + 5}px`) + .style("top", `${event.pageY - 28}px`); + + // Show the circle when hovered over the line + d3.select(this).style("opacity", 1); // Set opacity to 1 + }, + ) + .on("mousemove", (event: MouseEvent) => { tooltip - .html(`${d.value}`) .style("left", `${event.pageX + 5}px`) .style("top", `${event.pageY - 28}px`); + }) + .on("mouseout", function handleMouseOut() { + tooltip.transition().duration(0).style("opacity", 0); - // Show the circle when hovered over the line - d3.select(this).style("opacity", 1); // Set opacity to 1 - }, - ) - .on("mousemove", (event: MouseEvent) => { - tooltip - .style("left", `${event.pageX + 5}px`) - .style("top", `${event.pageY - 28}px`); - }) - .on("mouseout", function handleMouseOut() { - tooltip.transition().duration(0).style("opacity", 0); - - // Hide the circle when not hovering - d3.select(this).style("opacity", 0); // Set opacity back to 0 - }); + // Hide the circle when not hovering + d3.select(this).style("opacity", 0); // Set opacity back to 0 + }); + } } return () => window.removeEventListener("scroll", onScroll); @@ -330,9 +364,12 @@ export default function LineChart({ useEffect(() => { setNewData(updateNewData()); - setDataExists(newData.length !== 0); }, [data, updateNewData]); + useEffect(() => { + setDataExists(newData.length !== 0); + }, [newData]); + return (
-
+

{title}

{info !== "" && (
{ setInfoPopup(true); }} @@ -367,7 +404,7 @@ export default function LineChart({
)} +
+

{actualChange !== null && percentageChange && (actualChange < 0 - ? `⏷ \xa0 ${(actualChange * 100).toFixed(2)}%` - : `⏶ \xa0 ${(actualChange * 100).toFixed(2)}%`)} + ? `⏷ ${(actualChange * 100).toFixed(2)}%` + : `⏶ ${(actualChange * 100).toFixed(2)}%`)}

From c9e7d40b5c5d73d1accd6bd23a917b56f848542c Mon Sep 17 00:00:00 2001 From: Johannes Qian Date: Wed, 6 Nov 2024 18:35:19 -0500 Subject: [PATCH 14/26] Delete Test Users --- .../volunteer/approval/page.module.css | 6 ++--- src/app/api/patient/internal/seed/route.ts | 24 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/(management-portal)/volunteer/approval/page.module.css b/src/app/(management-portal)/volunteer/approval/page.module.css index 8cf8162c..f51a9b31 100644 --- a/src/app/(management-portal)/volunteer/approval/page.module.css +++ b/src/app/(management-portal)/volunteer/approval/page.module.css @@ -67,6 +67,6 @@ .netlify { position: absolute; - right: 5%; - bottom: 5%; -} + bottom: 20px; + right: 40px; +} \ No newline at end of file diff --git a/src/app/api/patient/internal/seed/route.ts b/src/app/api/patient/internal/seed/route.ts index df62132f..5bda07f3 100644 --- a/src/app/api/patient/internal/seed/route.ts +++ b/src/app/api/patient/internal/seed/route.ts @@ -2,6 +2,9 @@ import APIWrapper from "@server/utils/APIWrapper"; import User from "@server/mongodb/models/User"; import { IUser } from "@/common_utils/types"; import { createAnalyticsID } from "@server/mongodb/actions/Analytics"; +import Analytics from "@server/mongodb/models/Analytics"; +import { sampleUsers } from "@src/utils/patients"; +import mongoose from "mongoose"; export const dynamic = "force-dynamic"; @@ -29,19 +32,18 @@ export const POST = APIWrapper({ await createAnalyticsID(user._id); }), ); - // for (let i = 1; i < 10000; i++) { + // for (let i = 10000; i < 11000; i++) { // await Promise.all( - // sampleUsers.map(async (user: IUser) => { - // let deluser = await User.findOneAndDelete({ - // email: user.email + i.toString() + // sampleUsers.map(async (user: IUser) => { + // let deluser = await User.findOneAndDelete({ + // email: user.email + // }) + // if (deluser != undefined) { + // await Analytics.deleteOne({ + // userID: new mongoose.Types.ObjectId(deluser._id) + // }) + // } // }) - // if (deluser != undefined) { - // await Analytics.deleteOne({ - // userId: deluser._id - // }) - // } - - // }) // ) // } From a54db703dea9a7d48cf9d9f55ce54033ad504227 Mon Sep 17 00:00:00 2001 From: Sophia Lin <52588981+sophiazlin@users.noreply.github.com> Date: Sun, 10 Nov 2024 22:19:45 -0500 Subject: [PATCH 15/26] hover styling (#150) * hover styling * more hover styling * styling 3 * styling 4 * styling 5 --- .../AccountEditModal.module.css | 10 +++--- src/components/AccountEditModal/Password.tsx | 13 ++++++++ src/components/AccountEditModal/Profile.tsx | 26 ++++++++++++--- .../AddChapterModal.module.css | 5 +++ .../AddChapterModal/AddChapterModal.tsx | 13 +++++++- .../AddVolunteerModal.module.css | 5 +++ .../AddVolunteerModal/AddVolunteerModal.tsx | 2 +- .../ChapterGrid/ChapterGrid.module.css | 4 +++ src/components/ChapterGrid/Row/Row.module.css | 4 +++ .../ChapterInfo/Cell/Cell.module.css | 3 ++ src/components/ChapterInfo/Cell/Cell.tsx | 6 +++- src/components/ChapterInfo/ChapterInfo.tsx | 29 +++++++++++----- .../Popup/Popup.module.css | 6 ++++ .../ChapterVolunteerGrid/Row/Row.module.css | 11 +++++++ .../ChapterVolunteerGrid/Row/Row.tsx | 7 +++- .../DeleteChapterModal.module.css | 4 +++ .../ApplyDropdown/ApplyDropdown.module.css | 4 +++ .../Dropdown/AuthDropdown/AuthDropdown.tsx | 5 +++ src/components/Dropdown/Dropdown.module.css | 4 +++ src/components/Dropdown/Dropdown.tsx | 33 +++++++++++++++++-- .../EditChapterModal.module.css | 3 ++ .../EditChapterModal/EditChapterModal.tsx | 6 +++- .../InputField/InputField.module.css | 10 ++++-- src/components/InputField/InputField.tsx | 31 +++++++++++++++-- .../LiveSearchDropdown/LiveSearchDropdown.tsx | 5 +++ .../NavigationPanel.module.css | 12 ++++++- .../NavigationPanel/NavigationPanel.tsx | 30 ++++++++++++++--- .../PatientGrid/PatientGrid.module.css | 4 +++ src/components/PatientGrid/Row/Row.module.css | 11 +++++++ src/components/PatientGrid/Row/Row.tsx | 14 ++++++-- .../AdvancedSearch/AdvancedSearch.module.css | 19 +++++++++++ .../Search/AdvancedSearch/AdvancedSearch.tsx | 8 ++--- .../TransferChapterModal.module.css | 3 ++ .../TransferChapterModal.tsx | 2 +- .../VolunteerApprovalGrid/Row/Row.module.css | 6 ++++ .../VolunteerGrid/Popup/Popup.module.css | 6 ++++ .../VolunteerGrid/Row/Row.module.css | 11 +++++++ src/components/VolunteerGrid/Row/Row.tsx | 7 +++- .../VolunteerAdvancedSearch.module.css | 4 +++ 39 files changed, 341 insertions(+), 45 deletions(-) diff --git a/src/components/AccountEditModal/AccountEditModal.module.css b/src/components/AccountEditModal/AccountEditModal.module.css index a9bbe608..85a89931 100644 --- a/src/components/AccountEditModal/AccountEditModal.module.css +++ b/src/components/AccountEditModal/AccountEditModal.module.css @@ -91,17 +91,15 @@ border: 1px solid var(--Dark-Blue-Grey, #9ca5c2); } .nonEditable { - background-color: #e7eeff; - padding-left: 15px; - border: 0cm; + /* background-color: #e7eeff; */ + /* border: 0cm; */ color: #8f9bba; cursor: not-allowed; } .editable { - background-color: white; - padding-left: 15px; - border: 1px solid #979797; + /* background-color: white; */ + /* border: 1px solid #979797; */ color: #2b3674; } .inputField label { diff --git a/src/components/AccountEditModal/Password.tsx b/src/components/AccountEditModal/Password.tsx index febb5106..74ac6f23 100644 --- a/src/components/AccountEditModal/Password.tsx +++ b/src/components/AccountEditModal/Password.tsx @@ -22,6 +22,8 @@ export default function Password({ setShowSuccessModal }: Props) { const [newPasswordError, setNewPasswordError] = useState(""); const [confirmNewPasswordError, setConfirmNewPasswordError] = useState(""); + const [resetChangeTriggers, setResetChangeTriggers] = useState(false); + const resetErrors = () => { setOldPasswordError(""); setNewPasswordError(""); @@ -33,6 +35,7 @@ export default function Password({ setShowSuccessModal }: Props) { setNewPassword(""); setConfirmNewPassword(""); resetErrors(); + setResetChangeTriggers(!resetChangeTriggers); }; const handleSaveChanges = async ( @@ -41,6 +44,7 @@ export default function Password({ setShowSuccessModal }: Props) { e.preventDefault(); resetErrors(); let error = false; + setResetChangeTriggers(!resetChangeTriggers); if (oldPassword === "") { setOldPasswordError("Old password cannot be blank"); error = true; @@ -97,6 +101,9 @@ export default function Password({ setShowSuccessModal }: Props) { type="password" showError={oldPasswordError !== ""} error={oldPasswordError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={resetChangeTriggers} />
@@ -108,6 +115,9 @@ export default function Password({ setShowSuccessModal }: Props) { type="password" showError={newPasswordError !== ""} error={newPasswordError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={resetChangeTriggers} />
@@ -120,6 +130,9 @@ export default function Password({ setShowSuccessModal }: Props) { type="password" showError={confirmNewPasswordError !== ""} error={confirmNewPasswordError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={resetChangeTriggers} />
diff --git a/src/components/AccountEditModal/Profile.tsx b/src/components/AccountEditModal/Profile.tsx index 31af902c..b7756069 100644 --- a/src/components/AccountEditModal/Profile.tsx +++ b/src/components/AccountEditModal/Profile.tsx @@ -12,6 +12,7 @@ import { internalRequest } from "@src/utils/requests"; import { HttpMethod, IUser } from "@/common_utils/types"; import styles from "./AccountEditModal.module.css"; import Chip from "../Chip/Chip"; +import InputField from "../InputField/InputField"; interface Props { setShowSuccessModal: (args: boolean) => void; @@ -314,29 +315,35 @@ export default function Profile({ setShowSuccessModal }: Props) {
- setUpdatedFirstName(e.target.value)} className={!edit ? styles.nonEditable : styles.editable} readOnly={!edit} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={edit} />
- setUpdatedLastName(e.target.value)} className={!edit ? styles.nonEditable : styles.editable} readOnly={!edit} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={edit} />
- { @@ -345,12 +352,15 @@ export default function Profile({ setShowSuccessModal }: Props) { }} className={!edit ? styles.nonEditable : styles.editable} readOnly={!edit} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={edit} />
- { @@ -358,17 +368,23 @@ export default function Profile({ setShowSuccessModal }: Props) { }} className={!edit ? styles.nonEditable : styles.editable} readOnly={!edit} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={edit} />
- setUpdatedEmail(e.target.value)} className={!edit ? styles.nonEditable : styles.editable} readOnly={!edit} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={edit} />
{edit && ( diff --git a/src/components/AddChapterModal/AddChapterModal.module.css b/src/components/AddChapterModal/AddChapterModal.module.css index ca618d19..5dfd760c 100644 --- a/src/components/AddChapterModal/AddChapterModal.module.css +++ b/src/components/AddChapterModal/AddChapterModal.module.css @@ -124,6 +124,11 @@ color: white; cursor: pointer; } + +.submitButton:hover { + opacity: 50%; +} + .disabled { background-color: #e3eafc; color: #2b3674; diff --git a/src/components/AddChapterModal/AddChapterModal.tsx b/src/components/AddChapterModal/AddChapterModal.tsx index 866b3af7..a1825ed1 100644 --- a/src/components/AddChapterModal/AddChapterModal.tsx +++ b/src/components/AddChapterModal/AddChapterModal.tsx @@ -62,6 +62,8 @@ const AddChapterModal = ({ useState(); const [loading, setLoading] = useState(false); + const [resetChangeTriggers, setResetChangeTriggers] = useState(false); + const COUNTRIES = Country.getAllCountries() .sort((a, b) => { if (a.name === "United States") { @@ -110,6 +112,7 @@ const AddChapterModal = ({ setLocCity(""); setChapterPresidentObject(null); resetErrors(); + setResetChangeTriggers(!resetChangeTriggers); }; useEffect(() => { @@ -150,6 +153,7 @@ const AddChapterModal = ({ ) => { e.preventDefault(); resetErrors(); + setResetChangeTriggers(!resetChangeTriggers); let error = false; if (chapterName === "") { @@ -215,6 +219,9 @@ const AddChapterModal = ({ onChange={(e) => setChapterName(e.target.value)} showError={chapterNameError !== ""} error={chapterNameError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={resetChangeTriggers} />
@@ -235,6 +242,7 @@ const AddChapterModal = ({ }} showError={countryError !== ""} error={countryError} + resetChangeTriggers={resetChangeTriggers} />
{locCountry === "" ? null : ( @@ -252,6 +260,7 @@ const AddChapterModal = ({ }} showError={stateError !== ""} error={stateError} + resetChangeTriggers={resetChangeTriggers} />
)} @@ -291,6 +301,7 @@ const AddChapterModal = ({ }} showError={chapterPresidentError !== ""} error={chapterPresidentError} + resetChangeTriggers={resetChangeTriggers} />
@@ -299,7 +310,7 @@ const AddChapterModal = ({ onClick={reset} className={`${styles.submitButton} ${styles.disabled}`} > - Discard + Cancel
-
+

{cell.title}

diff --git a/src/components/ChapterInfo/ChapterInfo.tsx b/src/components/ChapterInfo/ChapterInfo.tsx index 50d3f4e0..d3ea88e4 100644 --- a/src/components/ChapterInfo/ChapterInfo.tsx +++ b/src/components/ChapterInfo/ChapterInfo.tsx @@ -8,8 +8,10 @@ import { Wrench, HandTransferIcon, } from "@src/app/icons"; -import { IChapter } from "@/common_utils/types"; +import { IChapter, IUser, Role } from "@/common_utils/types"; +import { RootState } from "@src/redux/rootReducer"; import { useMemo, useState } from "react"; +import { useSelector } from "react-redux"; import Modal from "@src/components/Modal/Modal"; import EditChapterModal from "@src/components/EditChapterModal/EditChapterModal"; import DeleteChapterModal from "@src/components/DeleteChapterModal/DeleteChapterModal"; @@ -32,6 +34,8 @@ interface ChapterInfoProps { } export default function ChapterInfo(params: ChapterInfoProps) { + const user = useSelector((state) => state.auth) as IUser; + const [showEditModal, setShowEditModal] = useState(false); const [showEditSuccessModal, setShowEditSuccessModal] = useState(false); const [editSuccessLink, setEditSuccessLink] = useState(""); @@ -93,23 +97,32 @@ export default function ChapterInfo(params: ChapterInfoProps) { title: "Edit Chapter Profile", link: () => setShowEditModal(true), icon: , + hoverColor: "#2B3674", }, { - title: "Chapter Transfer", + title: "Leadership Transfer", link: () => setShowTransferModal(true), icon: , + hoverColor: "#2B3674", }, { title: "Add Volunteer", link: () => setShowAddVolunteerModal(true), icon: , + hoverColor: "#2B3674", }, - { - title: "Delete Chapter", - link: () => setShowDeleteModal(true), - icon: , - iconStyle: { backgroundColor: "#FCDCE2" }, - }, + ...(user.role === Role.NONPROFIT_DIRECTOR || + user.role === Role.NONPROFIT_ADMIN + ? [ + { + title: "Delete Chapter", + link: () => setShowDeleteModal(true), + icon: , + iconStyle: { backgroundColor: "#FCDCE2" }, + hoverColor: "#EA4335", + }, + ] + : []), ] as CellProps[]; }, [params.chapter]); diff --git a/src/components/ChapterVolunteerGrid/Popup/Popup.module.css b/src/components/ChapterVolunteerGrid/Popup/Popup.module.css index d790f2b6..176b88cd 100644 --- a/src/components/ChapterVolunteerGrid/Popup/Popup.module.css +++ b/src/components/ChapterVolunteerGrid/Popup/Popup.module.css @@ -65,6 +65,9 @@ font-style: normal; font-weight: 700; } +.confirmButton:hover { + opacity: 50%; +} .cancelButton { background-color: #e3e3e3; @@ -80,3 +83,6 @@ font-style: normal; font-weight: 700; } +.cancelButton:hover { + opacity: 50%; +} diff --git a/src/components/ChapterVolunteerGrid/Row/Row.module.css b/src/components/ChapterVolunteerGrid/Row/Row.module.css index b13a880f..11183a71 100644 --- a/src/components/ChapterVolunteerGrid/Row/Row.module.css +++ b/src/components/ChapterVolunteerGrid/Row/Row.module.css @@ -33,6 +33,14 @@ display: none; } +.RowCellText { + width: fit-content; +} + +.RowCellText:hover { + cursor: pointer; +} + .ExpandedRowContainer { width: calc(100% + 20px); border-radius: 5px; @@ -118,3 +126,6 @@ cursor: pointer; transition: background-color 0.2s; } +.deleteButton:hover { + opacity: 50%; +} diff --git a/src/components/ChapterVolunteerGrid/Row/Row.tsx b/src/components/ChapterVolunteerGrid/Row/Row.tsx index 99eb8ed7..e97f798e 100644 --- a/src/components/ChapterVolunteerGrid/Row/Row.tsx +++ b/src/components/ChapterVolunteerGrid/Row/Row.tsx @@ -141,6 +141,8 @@ export function Row({ volunteer, handleDeleteClick }: Props) {
diff --git a/src/components/DeleteChapterModal/DeleteChapterModal.module.css b/src/components/DeleteChapterModal/DeleteChapterModal.module.css index 4ff4e692..2261c869 100644 --- a/src/components/DeleteChapterModal/DeleteChapterModal.module.css +++ b/src/components/DeleteChapterModal/DeleteChapterModal.module.css @@ -54,6 +54,10 @@ color: white; cursor: pointer; } + +.submitButton:hover { + opacity: 50%; +} .disabled { background-color: #e3eafc; color: #2b3674; diff --git a/src/components/Dropdown/ApplyDropdown/ApplyDropdown.module.css b/src/components/Dropdown/ApplyDropdown/ApplyDropdown.module.css index 8807bb05..e12468be 100644 --- a/src/components/Dropdown/ApplyDropdown/ApplyDropdown.module.css +++ b/src/components/Dropdown/ApplyDropdown/ApplyDropdown.module.css @@ -42,6 +42,10 @@ align-items: center; } +.applyButton:hover { + opacity: 50%; +} + .checkIcon { font-size: 14px; display: flex; diff --git a/src/components/Dropdown/AuthDropdown/AuthDropdown.tsx b/src/components/Dropdown/AuthDropdown/AuthDropdown.tsx index 8cb1990b..fef8b369 100644 --- a/src/components/Dropdown/AuthDropdown/AuthDropdown.tsx +++ b/src/components/Dropdown/AuthDropdown/AuthDropdown.tsx @@ -21,6 +21,7 @@ export interface DropdownProps { error?: string; placeholder?: string; onChange: (e: SelectChangeEvent) => void; + resetChangeTriggers?: string | boolean; } export default function AuthDropdown(props: DropdownProps) { @@ -35,6 +36,7 @@ export default function AuthDropdown(props: DropdownProps) { showError, error, onChange, + resetChangeTriggers, } = props; const [isOpen, setIsOpen] = useState(false); @@ -70,6 +72,9 @@ export default function AuthDropdown(props: DropdownProps) { }, }, }} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" + resetChangeTrigger={resetChangeTriggers} /> {showError && error !== undefined && (
diff --git a/src/components/Dropdown/Dropdown.module.css b/src/components/Dropdown/Dropdown.module.css index fb7aabd1..76cd154b 100644 --- a/src/components/Dropdown/Dropdown.module.css +++ b/src/components/Dropdown/Dropdown.module.css @@ -5,7 +5,11 @@ .input-field { height: 40px; + background-color: var(--dropdown-background-color) !important; } +/* .input-field:hover { + background-color: var(--dropdown-hover-color) !important; +} */ .defaultMenuItem { width: 100%; diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index f1a3ce9a..af058dd3 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -1,6 +1,12 @@ "use client"; -import React, { ReactNode, useCallback, useMemo } from "react"; +import React, { + ReactNode, + useCallback, + useMemo, + useState, + useEffect, +} from "react"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { classes } from "@src/utils/utils"; @@ -40,6 +46,9 @@ export interface DropdownProps { menuItemStyle?: React.CSSProperties; style?: React.CSSProperties; selectProps?: SelectProps; + defaultBackgroundColor?: string; + hoverColor?: string; + resetChangeTrigger?: string | boolean; } const poppins400 = Poppins({ @@ -60,6 +69,8 @@ const StyledSelect = styled(Select)(() => ({ })); function Dropdown(props: DropdownProps) { + const [changeTriggered, setChangeTriggered] = useState(false); + const { className, options, @@ -72,10 +83,18 @@ function Dropdown(props: DropdownProps) { menuItemStyle, style, selectProps, + defaultBackgroundColor, + hoverColor, + resetChangeTrigger, } = props; + useEffect(() => { + setChangeTriggered(false); + }, [resetChangeTrigger, setChangeTriggered]); + const onSelectChange = useCallback( (e: SelectChangeEvent) => { + setChangeTriggered(true); onChange(e); }, [onChange], @@ -107,7 +126,17 @@ function Dropdown(props: DropdownProps) { ); return ( -
+
setYearFounded(e.target.value)} showError={yearFoundedError !== ""} error={yearFoundedError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" />
@@ -236,6 +238,8 @@ const EditChapterModal = ({ onChange={(e) => setChapterName(e.target.value)} showError={chapterNameError !== ""} error={chapterNameError} + defaultBackgroundColor="#e3eafc" + hoverColor="#ffffff" />
@@ -295,7 +299,7 @@ const EditChapterModal = ({ onClick={reset} className={`${styles.submitButton} ${styles.disabled}`} > - Clear + Cancel -
{row.firstName}
+
+

+ {row.firstName} +

+
-
{row.lastName}
+
+

+ {row.lastName} +

+
{row.dateOfBirth}
diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.module.css b/src/components/Search/AdvancedSearch/AdvancedSearch.module.css index 006eb1c8..902b575d 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.module.css +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.module.css @@ -112,6 +112,10 @@ background-color: #f4f7fe; } +.select-dropdown-answer:hover { + background-color: white; +} + .answer { border: 1px solid #d9d9d9 !important; border-top-right-radius: 10px; @@ -120,6 +124,10 @@ background-color: #f4f7fe; } +.answer:hover { + background-color: white; +} + .answerInput { height: 31px !important; /* border-radius: 10px !important; */ @@ -131,6 +139,10 @@ background-color: #f4f7fe; } +.answerInput:hover { + background-color: white; +} + .secondaryInfo { display: flex; flex-direction: column; @@ -174,6 +186,10 @@ padding: 8px 12px; } +.general-button:hover { + opacity: 50%; +} + .add-button { background-color: #008afc; color: white; @@ -214,6 +230,9 @@ color: #2b3674; font-weight: 600; } +.inactive-button:hover { + background-color: white; +} .clear-button { background-color: white; color: #2b3674; diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index 65483ad8..390a21e8 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -59,7 +59,7 @@ function SelectDropdown({ width: "100%", border: "none", borderRadius: 10, - backgroundColor: "#F4F7FE", + backgroundColor: "transparent", }} sx={{ "&.MuiOutlinedInput-root": { @@ -300,21 +300,21 @@ export const AdvancedSearch = (props: UpdateParamProp) => {
diff --git a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.module.css b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.module.css index fffb5c50..0610d477 100644 --- a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.module.css +++ b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.module.css @@ -178,6 +178,10 @@ padding: 8px 12px; } +.general-button:hover { + opacity: 50%; +} + .add-button { background-color: #008afc; color: white; From 42f513c23b0222e4b5820dc8fb008aef6196810d Mon Sep 17 00:00:00 2001 From: Johannes Qian <115521998+johannesq23@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:52:43 -0500 Subject: [PATCH 16/26] Add Max Calculation (#155) --- server/mongodb/actions/AggregatedAnalytics.ts | 141 +++++++++--------- server/utils/utils.ts | 17 ++- .../volunteer/approval/page.module.css | 2 +- src/app/api/patient/internal/seed/route.ts | 3 - src/components/Graphs/BarChart/BarChart.tsx | 14 +- .../Graphs/PopupModal/PopupModal.module.css | 5 +- 6 files changed, 101 insertions(+), 81 deletions(-) diff --git a/server/mongodb/actions/AggregatedAnalytics.ts b/server/mongodb/actions/AggregatedAnalytics.ts index 9e3d9465..fb601524 100644 --- a/server/mongodb/actions/AggregatedAnalytics.ts +++ b/server/mongodb/actions/AggregatedAnalytics.ts @@ -92,7 +92,6 @@ export const getAggregatedAnalytics = async ( let counter = 0; const lenOfMetrics = analyticsRecord.weeklyMetrics.length; - const groupSize = Math.floor(lenOfMetrics / 12); // reversing the list const reversedWeeklyMetrics = analyticsRecord.weeklyMetrics.reverse(); @@ -106,10 +105,6 @@ export const getAggregatedAnalytics = async ( reversedWeeklyMetrics.length === 0 ? lastMonday : new Date(reversedWeeklyMetrics[0].date); - let lastDateMax = - reversedWeeklyMetrics.length === 0 - ? lastMonday - : new Date(reversedWeeklyMetrics[0].date); const dbDateVars = new Set(); @@ -121,13 +116,14 @@ export const getAggregatedAnalytics = async ( dateVar = formatDateByRangeEnum(lastDate, range); } - // group into groups of 12 and put everything into the last object once it goes over if (range === DateRangeEnum.MAX) { - if (counter % groupSize === 0 && counter < groupSize * 12) { - lastDateMax = item.date; + if (lenOfMetrics > 12) { + dateVar = formatDateByRangeEnum(item.date, range, true); + } else { + dateVar = formatDateByRangeEnum(item.date, range); } - dateVar = formatDateByRangeEnum(lastDateMax, range); } + counter += 1; lastDate = item.date; @@ -231,6 +227,7 @@ export const getAggregatedAnalytics = async ( "questionsCompleted", "questionsCorrect", ]); + Object.values(groupSumDict).forEach((monthDict) => { Object.values(monthDict).forEach((monthTypeDict) => { Object.keys(monthTypeDict).forEach((property) => { @@ -260,70 +257,78 @@ export const getAggregatedAnalytics = async ( const allDateVars = Array.from(dbDateVars).reverse(); // PADDING - if (range !== DateRangeEnum.MAX) { - let len = Object.keys(groupSumDict).length; - - let totalWeeks = numOfWeeks; - if (range === DateRangeEnum.HALF) { - totalWeeks = 13; - } else if (range === DateRangeEnum.YEAR) { - totalWeeks = 12; - } - if ( - userIDs.length === 1 && - (analyticsRecords[0].weeklyMetrics as []).length === 0 - ) { + let len = Object.keys(groupSumDict).length; + + let totalWeeks = numOfWeeks; + if (range === DateRangeEnum.HALF) { + totalWeeks = 13; + } else if (range === DateRangeEnum.YEAR) { + totalWeeks = 12; + } else if (range === DateRangeEnum.MAX) { + if (lenOfMetrics === 1) { + totalWeeks = 6; + } else { totalWeeks = 0; } + } - while (len < totalWeeks) { - if (range === DateRangeEnum.RECENT || range === DateRangeEnum.QUARTER) { - paddingDate.setDate(paddingDate.getDate() - 7); - } else if (range === DateRangeEnum.HALF) { - paddingDate.setDate(paddingDate.getDate() - 14); - } else if (range === DateRangeEnum.YEAR) { - paddingDate.setMonth(paddingDate.getMonth() - 1); - } - - const tempDateString = formatDateByRangeEnum(paddingDate, range); - - allDateVars.push(tempDateString); - groupSumDict[tempDateString] = Object.fromEntries( - Object.entries({ - overall: { - streakLength: 0, - streakHistory: 0, - }, - math: { - avgAccuracy: 0, - avgDifficultyScore: 0, - avgQuestionsCompleted: 0, - avgTimePerQuestion: 0, - }, - reading: { - avgSessionsCompleted: 0, - avgSessionsAttempted: 0, - avgWordsPerMin: 0, - avgPassagesRead: 0, - avgTimePerPassage: 0, - }, - writing: { - avgSessionsCompleted: 0, - avgSessionsAttempted: 0, - avgPromptsAnswered: 0, - avgTimePerQuestion: 0, - }, - trivia: { - avgAccuracy: 0, - avgQuestionsCompleted: 0, - avgTimePerQuestion: 0, - }, - }).filter(([k]) => sections.includes(k as AnalyticsSectionEnum)), - ); + if ( + userIDs.length === 1 && + (analyticsRecords[0].weeklyMetrics as []).length === 0 + ) { + totalWeeks = 0; + } - len += 1; + while (len < totalWeeks) { + if (range === DateRangeEnum.RECENT || range === DateRangeEnum.QUARTER) { + paddingDate.setDate(paddingDate.getDate() - 7); + } else if (range === DateRangeEnum.HALF) { + paddingDate.setDate(paddingDate.getDate() - 14); + } else if (range === DateRangeEnum.YEAR) { + paddingDate.setMonth(paddingDate.getMonth() - 1); + } else if (range === DateRangeEnum.MAX) { + paddingDate.setDate(paddingDate.getDate() - 7); } + + const tempDateString = formatDateByRangeEnum(paddingDate, range); + + allDateVars.push(tempDateString); + groupSumDict[tempDateString] = Object.fromEntries( + Object.entries({ + overall: { + streakLength: 0, + streakHistory: 0, + }, + math: { + avgAccuracy: 0, + avgDifficultyScore: 0, + avgQuestionsCompleted: 0, + avgTimePerQuestion: 0, + }, + reading: { + avgSessionsCompleted: 0, + avgSessionsAttempted: 0, + avgWordsPerMin: 0, + avgPassagesRead: 0, + avgTimePerPassage: 0, + }, + writing: { + avgSessionsCompleted: 0, + avgSessionsAttempted: 0, + avgPromptsAnswered: 0, + avgTimePerQuestion: 0, + }, + trivia: { + avgAccuracy: 0, + avgQuestionsCompleted: 0, + avgTimePerQuestion: 0, + }, + }).filter(([k]) => sections.includes(k as AnalyticsSectionEnum)), + ); + + len += 1; } + // } const result: Result = {}; sections.forEach((type) => { @@ -416,7 +421,7 @@ export const getAggregatedAnalytics = async ( }; const obj = result.overall; if (obj) { - obj.streakHistory.push(dr); + obj.streakHistory.unshift(dr); } } diff --git a/server/utils/utils.ts b/server/utils/utils.ts index f6a88e9b..d80461b8 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -19,7 +19,12 @@ export function getCurrentSunday() { return new Date(date.toDateString()); } -export function formatDateByRangeEnum(date: Date, range: DateRangeEnum) { +export function formatDateByRangeEnum( + date: Date, + range: DateRangeEnum, + month?: boolean, +) { + const boolMonth = month || false; const monthName = date.toLocaleString("default", { timeZone: "UTC", month: "short", @@ -28,9 +33,13 @@ export function formatDateByRangeEnum(date: Date, range: DateRangeEnum) { const year = String(date.getUTCFullYear()); if ( - [DateRangeEnum.RECENT, DateRangeEnum.QUARTER, DateRangeEnum.HALF].includes( - range, - ) + [ + DateRangeEnum.RECENT, + DateRangeEnum.QUARTER, + DateRangeEnum.HALF, + DateRangeEnum.MAX, + ].includes(range) && + !boolMonth ) { return `${monthName} ${day}`; } diff --git a/src/app/(management-portal)/volunteer/approval/page.module.css b/src/app/(management-portal)/volunteer/approval/page.module.css index f51a9b31..0b32ff3c 100644 --- a/src/app/(management-portal)/volunteer/approval/page.module.css +++ b/src/app/(management-portal)/volunteer/approval/page.module.css @@ -69,4 +69,4 @@ position: absolute; bottom: 20px; right: 40px; -} \ No newline at end of file +} diff --git a/src/app/api/patient/internal/seed/route.ts b/src/app/api/patient/internal/seed/route.ts index 5bda07f3..a1c2e7ae 100644 --- a/src/app/api/patient/internal/seed/route.ts +++ b/src/app/api/patient/internal/seed/route.ts @@ -2,9 +2,6 @@ import APIWrapper from "@server/utils/APIWrapper"; import User from "@server/mongodb/models/User"; import { IUser } from "@/common_utils/types"; import { createAnalyticsID } from "@server/mongodb/actions/Analytics"; -import Analytics from "@server/mongodb/models/Analytics"; -import { sampleUsers } from "@src/utils/patients"; -import mongoose from "mongoose"; export const dynamic = "force-dynamic"; diff --git a/src/components/Graphs/BarChart/BarChart.tsx b/src/components/Graphs/BarChart/BarChart.tsx index 38b46ae3..6e0532b6 100644 --- a/src/components/Graphs/BarChart/BarChart.tsx +++ b/src/components/Graphs/BarChart/BarChart.tsx @@ -201,7 +201,10 @@ export default function BarChart({ .tickSizeOuter(0) .tickSizeInner(0) .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[0] ?? ""); + .tickFormat((d) => { + const index = Math.round(Number(d)); + return newData[index]?.interval?.split(" ")[0] ?? ""; + }); const xAxisLabelBottom = d3 .axisBottom(x) @@ -209,7 +212,10 @@ export default function BarChart({ .tickSizeOuter(0) .tickSizeInner(0) .tickPadding(15) - .tickFormat((d) => newData[d.valueOf()]?.interval?.split(" ")[1] ?? ""); + .tickFormat((d) => { + const index = Math.round(Number(d)); + return newData[index]?.interval?.split(" ")[1] ?? ""; + }); const yAxisLabel = d3 .axisLeft(y) @@ -336,8 +342,8 @@ export default function BarChart({ if (d.value === 0) { // Render a semi-circle (half-circle) if the value is 0 return ` - M ${x0},${y0} - A ${radius},${radius} 0 1 1 ${x0 + barWidth},${y0} + M ${x0},${height - marginBottom} + A ${radius},${radius} 0 1 1 ${x0 + barWidth},${height - marginBottom} `; } // Render a rectangle with a rounded top if the value is non-zero diff --git a/src/components/Graphs/PopupModal/PopupModal.module.css b/src/components/Graphs/PopupModal/PopupModal.module.css index fa989f6d..a8dff415 100644 --- a/src/components/Graphs/PopupModal/PopupModal.module.css +++ b/src/components/Graphs/PopupModal/PopupModal.module.css @@ -2,7 +2,10 @@ z-index: 50; background-color: #cdcdcd; width: 300px; - border-radius: 10px; + border-radius: 12px; padding: 10px; border: "0.8px solid black"; + overflow: visible; + white-space: normal; + word-wrap: break-word; } From a1be50123fcb8c7bd3255002899132b0685222ec Mon Sep 17 00:00:00 2001 From: nikvijay07 <113941701+nikvijay07@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:31:05 -0500 Subject: [PATCH 17/26] Nik/157 group analytics testing (#163) * deleted sample data and stacked bargraphs * cleaning up values and displaying active patients and total patients * removing pink bars * fixed overall graphs group * fixing error with individual patient * prettier and lint * trying to generate netlify comment lol * it didnt generate :( --- common_utils/types.ts | 69 ++++----- server/mongodb/actions/AggregatedAnalytics.ts | 135 +++++++++++------- server/mongodb/actions/Analytics.ts | 14 +- server/utils/utils.ts | 9 +- .../patient/analytics/group/page.tsx | 45 +++--- src/app/api/patient/analytics/group/route.ts | 10 +- src/app/api/patient/analytics/route.ts | 5 +- src/app/api/patient/auth/login/route.ts | 2 +- .../OverviewReport/OverviewReport.tsx | 4 +- src/components/Graphs/BarChart/BarChart.tsx | 28 +--- .../GroupMathScreen/GroupMathScreen.tsx | 13 +- .../GroupOverviewReport.tsx | 24 ++-- .../GroupReadingScreen.module.css | 5 +- .../GroupReadingScreen/GroupReadingScreen.tsx | 28 +--- .../GroupTriviaScreen/GroupTriviaScreen.tsx | 10 +- .../GroupWritingScreen/GroupWritingScreen.tsx | 25 +--- 16 files changed, 211 insertions(+), 215 deletions(-) diff --git a/common_utils/types.ts b/common_utils/types.ts index ef4c17b1..a1a2e582 100644 --- a/common_utils/types.ts +++ b/common_utils/types.ts @@ -117,38 +117,36 @@ export interface IAnalytics { totalSessionsCompleted: number; active: boolean; streak: Days[]; - lastSessionsMetrics: [ - { - date: Date; - math: { - attempted: boolean; - questionsAttempted: number; - questionsCorrect: number; - finalDifficultyScore: number; - timePerQuestion: number; - }; - trivia: { - attempted: boolean; - questionsAttempted: number; - questionsCorrect: number; - timePerQuestion: number; - }; - reading: { - attempted: boolean; - passagesRead: number; - timePerPassage: number; - wordsPerMinute: number; - questionsAnswered: number; - skipped: boolean; - }; - writing: { - attempted: boolean; - questionsAnswered: number; - timePerQuestion: number; - skipped: boolean; - }; - }, - ]; + lastSessionMetrics: { + date: Date; + math: { + attempted: boolean; + questionsAttempted: number; + questionsCorrect: number; + finalDifficultyScore: number; + timePerQuestion: number; + }; + trivia: { + attempted: boolean; + questionsAttempted: number; + questionsCorrect: number; + timePerQuestion: number; + }; + reading: { + attempted: boolean; + passagesRead: number; + timePerPassage: number; + wordsPerMinute: number; + questionsAnswered: number; + skipped: boolean; + }; + writing: { + attempted: boolean; + questionsAnswered: number; + timePerQuestion: number; + skipped: boolean; + }; + }; weeklyMetrics: [ { date: Date; @@ -351,6 +349,7 @@ export interface IAggregatedAnalyticsAll IAggregatedAnalyticsMath, IAggregatedAnalyticsTrivia, IAggregatedAnalyticsReading, + PatientStats, IAggregatedAnalyticsWriting {} export interface IAggregatedAnalyticsOverall { @@ -436,6 +435,7 @@ export interface IAggregatedGroupAnalyticsAll IAggregatedGroupAnalyticsMath, IAggregatedGroupAnalyticsReading, IAggregatedGroupAnalyticsWriting, + PatientStats, IAggregatedGroupAnalyticsTrivia {} export type IAggregatedGroupAnalyticsOverall = { @@ -466,6 +466,11 @@ export type IAggregatedGroupAnalyticsWriting = { }; }; +export interface PatientStats { + totalPatients: number; + activePatients: number; +} + export interface IGeneralReducer { pendingApprovals: number; } diff --git a/server/mongodb/actions/AggregatedAnalytics.ts b/server/mongodb/actions/AggregatedAnalytics.ts index fb601524..77280bda 100644 --- a/server/mongodb/actions/AggregatedAnalytics.ts +++ b/server/mongodb/actions/AggregatedAnalytics.ts @@ -47,11 +47,17 @@ type Result = Partial<{ }; }>; +type AggregatedAnalyticsResponse = { + analytics: Partial[]; // The array list + activePatients: number; // The first additional attribute + totalPatients: number; // The second additional attribute +}; + export const getAggregatedAnalytics = async ( userIDs: string[], range: DateRangeEnum, sections: AnalyticsSectionEnum[], -): Promise[]> => { +): Promise => { let numOfWeeks = Number.MAX_SAFE_INTEGER; if (range === DateRangeEnum.RECENT) { @@ -63,7 +69,6 @@ export const getAggregatedAnalytics = async ( } else if (range === DateRangeEnum.YEAR) { numOfWeeks = 52; // 52 } - const objectIdArray = userIDs.map((id) => new mongoose.Types.ObjectId(id)); const userRecords = await User.find( { _id: { $in: objectIdArray } }, @@ -77,6 +82,43 @@ export const getAggregatedAnalytics = async ( .limit(1000) .lean(); + let activeUsers: { count: number }[] = []; + + if (userIDs.length > 1) { + activeUsers = await User.aggregate([ + { + $match: { + _id: { $in: objectIdArray }, + }, + }, + { + $lookup: { + from: "analytics", + localField: "_id", + foreignField: "userID", + as: "analyticsRecords", + }, + }, + { + $unwind: { + path: "$analyticsRecords", + preserveNullAndEmptyArrays: true, + }, + }, + { + $match: { + "analyticsRecords.active": true, + }, + }, + { + $group: { + _id: null, + count: { $sum: 1 }, + }, + }, + ]); + } + if (!analyticsRecords) { throw new Error("User Analytics record not found"); } @@ -90,7 +132,6 @@ export const getAggregatedAnalytics = async ( const groupSumDict: Record = {}; - let counter = 0; const lenOfMetrics = analyticsRecord.weeklyMetrics.length; // reversing the list @@ -101,19 +142,14 @@ export const getAggregatedAnalytics = async ( ? lastMonday : new Date(reversedWeeklyMetrics[0].date); - let lastDate = - reversedWeeklyMetrics.length === 0 - ? lastMonday - : new Date(reversedWeeklyMetrics[0].date); - const dbDateVars = new Set(); reversedWeeklyMetrics.forEach((item: IAnalytics["weeklyMetrics"][0]) => { let dateVar = formatDateByRangeEnum(item.date, range); // adds to previous date in groups of 2 - if (range === DateRangeEnum.HALF && counter % 2 === 1) { - dateVar = formatDateByRangeEnum(lastDate, range); + if (range === DateRangeEnum.HALF) { + dateVar = formatDateByRangeEnum(item.date, range, true); } if (range === DateRangeEnum.MAX) { @@ -124,9 +160,6 @@ export const getAggregatedAnalytics = async ( } } - counter += 1; - lastDate = item.date; - if (!dbDateVars.has(dateVar)) { dbDateVars.add(dateVar); } @@ -221,7 +254,6 @@ export const getAggregatedAnalytics = async ( } }); }); - const excludedProperties = new Set([ "totalNum", "questionsCompleted", @@ -261,7 +293,7 @@ export const getAggregatedAnalytics = async ( let totalWeeks = numOfWeeks; if (range === DateRangeEnum.HALF) { - totalWeeks = 13; + totalWeeks = 6; } else if (range === DateRangeEnum.YEAR) { totalWeeks = 12; } else if (range === DateRangeEnum.MAX) { @@ -283,13 +315,12 @@ export const getAggregatedAnalytics = async ( if (range === DateRangeEnum.RECENT || range === DateRangeEnum.QUARTER) { paddingDate.setDate(paddingDate.getDate() - 7); } else if (range === DateRangeEnum.HALF) { - paddingDate.setDate(paddingDate.getDate() - 14); + paddingDate.setMonth(paddingDate.getMonth() - 1); } else if (range === DateRangeEnum.YEAR) { paddingDate.setMonth(paddingDate.getMonth() - 1); } else if (range === DateRangeEnum.MAX) { paddingDate.setDate(paddingDate.getDate() - 7); } - const tempDateString = formatDateByRangeEnum(paddingDate, range); allDateVars.push(tempDateString); @@ -328,7 +359,6 @@ export const getAggregatedAnalytics = async ( len += 1; } - // } const result: Result = {}; sections.forEach((type) => { @@ -459,19 +489,17 @@ export const getAggregatedAnalytics = async ( active: analyticsRecord.active, streak: analyticsRecord.streak, startDate: user?.startDate ? new Date(user.startDate) : new Date(), - lastSessionDate: analyticsRecord.lastSessionsMetrics[0].date, + lastSessionDate: analyticsRecord.lastSessionMetrics.date, totalSessionsCompleted: analyticsRecord.totalSessionsCompleted, lastSession: { mathQuestionsCompleted: - analyticsRecord.lastSessionsMetrics[0].math.questionsAttempted, + analyticsRecord.lastSessionMetrics.math.questionsAttempted, wordsRead: - analyticsRecord.lastSessionsMetrics[0].reading.passagesRead, + analyticsRecord.lastSessionMetrics.reading.passagesRead, promptsCompleted: - analyticsRecord.lastSessionsMetrics[0].writing - .questionsAnswered, // writing + analyticsRecord.lastSessionMetrics.writing.questionsAnswered, // writing triviaQuestionsCompleted: - analyticsRecord.lastSessionsMetrics[0].trivia - .questionsAttempted, + analyticsRecord.lastSessionMetrics.trivia.questionsAttempted, }, name: `${user.firstName} ${user.lastName}`, }; @@ -483,20 +511,16 @@ export const getAggregatedAnalytics = async ( ...result.math, lastSession: { accuracy: - analyticsRecord.lastSessionsMetrics[0].math - .questionsAttempted === 0 + analyticsRecord.lastSessionMetrics.math.questionsAttempted === 0 ? 0 - : analyticsRecord.lastSessionsMetrics[0].math - .questionsCorrect / - analyticsRecord.lastSessionsMetrics[0].math - .questionsAttempted, + : analyticsRecord.lastSessionMetrics.math.questionsCorrect / + analyticsRecord.lastSessionMetrics.math.questionsAttempted, difficultyScore: - analyticsRecord.lastSessionsMetrics[0].math - .finalDifficultyScore, + analyticsRecord.lastSessionMetrics.math.finalDifficultyScore, questionsCompleted: - analyticsRecord.lastSessionsMetrics[0].math.questionsAttempted, + analyticsRecord.lastSessionMetrics.math.questionsAttempted, timePerQuestion: - analyticsRecord.lastSessionsMetrics[0].math.timePerQuestion, + analyticsRecord.lastSessionMetrics.math.timePerQuestion, }, }; break; @@ -507,12 +531,12 @@ export const getAggregatedAnalytics = async ( ...result.reading, lastSession: { passagesRead: - analyticsRecord.lastSessionsMetrics[0].reading.passagesRead, + analyticsRecord.lastSessionMetrics.reading.passagesRead, timePerPassage: - analyticsRecord.lastSessionsMetrics[0].reading.timePerPassage, + analyticsRecord.lastSessionMetrics.reading.timePerPassage, completed: - analyticsRecord.lastSessionsMetrics[0].reading - .questionsAnswered !== 0, + analyticsRecord.lastSessionMetrics.reading.questionsAnswered !== + 0, }, }; break; @@ -523,13 +547,12 @@ export const getAggregatedAnalytics = async ( ...result.writing, lastSession: { promptsAnswered: - analyticsRecord.lastSessionsMetrics[0].writing - .questionsAnswered, + analyticsRecord.lastSessionMetrics.writing.questionsAnswered, timePerPrompt: - analyticsRecord.lastSessionsMetrics[0].writing.timePerQuestion, + analyticsRecord.lastSessionMetrics.writing.timePerQuestion, completed: - analyticsRecord.lastSessionsMetrics[0].writing - .questionsAnswered !== 0, + analyticsRecord.lastSessionMetrics.writing.questionsAnswered !== + 0, }, }; break; @@ -540,18 +563,16 @@ export const getAggregatedAnalytics = async ( ...result.trivia, lastSession: { accuracy: - analyticsRecord.lastSessionsMetrics[0].trivia - .questionsAttempted === 0 + analyticsRecord.lastSessionMetrics.trivia.questionsAttempted === + 0 ? 0 - : analyticsRecord.lastSessionsMetrics[0].trivia - .questionsCorrect / - analyticsRecord.lastSessionsMetrics[0].trivia + : analyticsRecord.lastSessionMetrics.trivia.questionsCorrect / + analyticsRecord.lastSessionMetrics.trivia .questionsAttempted, questionsCompleted: - analyticsRecord.lastSessionsMetrics[0].trivia - .questionsAttempted, + analyticsRecord.lastSessionMetrics.trivia.questionsAttempted, timePerQuestion: - analyticsRecord.lastSessionsMetrics[0].trivia.timePerQuestion, + analyticsRecord.lastSessionMetrics.trivia.timePerQuestion, }, }; break; @@ -560,8 +581,16 @@ export const getAggregatedAnalytics = async ( break; } }); + out.push(finalAggregation); } + const finalOut: AggregatedAnalyticsResponse = { + analytics: out, + totalPatients: userIDs.length, + activePatients: activeUsers[0]?.count || 0, + }; + + // console.log(finalOut) - return out; + return finalOut; }; diff --git a/server/mongodb/actions/Analytics.ts b/server/mongodb/actions/Analytics.ts index 8dda3bcb..e9bb3f5f 100644 --- a/server/mongodb/actions/Analytics.ts +++ b/server/mongodb/actions/Analytics.ts @@ -24,9 +24,9 @@ const checkNewDate = async (userID: string): Promise => { const today = new Date(); if ( - analytics?.lastSessionsMetrics[0].date.getDay() !== today.getDay() || - analytics?.lastSessionsMetrics[0].date.getMonth() !== today.getMonth() || - analytics?.lastSessionsMetrics[0].date.getFullYear() !== today.getFullYear() + analytics?.lastSessionMetrics.date.getDay() !== today.getDay() || + analytics?.lastSessionMetrics.date.getMonth() !== today.getMonth() || + analytics?.lastSessionMetrics.date.getFullYear() !== today.getFullYear() ) { await Analytics.findOneAndUpdate({ userID }, [ { @@ -117,10 +117,10 @@ const checkSessionComplete = async (userID: string): Promise => { const analytics = await Analytics.findOne({ userID }); if ( - analytics?.lastSessionsMetrics[0].math.attempted && - analytics?.lastSessionsMetrics[0].trivia.attempted && - analytics?.lastSessionsMetrics[0].reading.attempted && - analytics?.lastSessionsMetrics[0].writing.attempted + analytics?.lastSessionMetrics.math.attempted && + analytics?.lastSessionMetrics.trivia.attempted && + analytics?.lastSessionMetrics.reading.attempted && + analytics?.lastSessionMetrics.writing.attempted ) { await updateSessionComplete(userID); } diff --git a/server/utils/utils.ts b/server/utils/utils.ts index d80461b8..27a8231b 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -33,12 +33,9 @@ export function formatDateByRangeEnum( const year = String(date.getUTCFullYear()); if ( - [ - DateRangeEnum.RECENT, - DateRangeEnum.QUARTER, - DateRangeEnum.HALF, - DateRangeEnum.MAX, - ].includes(range) && + [DateRangeEnum.RECENT, DateRangeEnum.QUARTER, DateRangeEnum.MAX].includes( + range, + ) && !boolMonth ) { return `${monthName} ${day}`; diff --git a/src/app/(management-portal)/patient/analytics/group/page.tsx b/src/app/(management-portal)/patient/analytics/group/page.tsx index 7b48c635..eab4780d 100644 --- a/src/app/(management-portal)/patient/analytics/group/page.tsx +++ b/src/app/(management-portal)/patient/analytics/group/page.tsx @@ -19,12 +19,6 @@ import { IAggregatedAnalyticsWriting, PatientSearchParams, } from "@/common_utils/types"; -import { - dataBar, - dataLine, - dataStacked, - numberOfQuestionData, -} from "@src/utils/patients"; import Modal from "@src/components/Modal/Modal"; import LoadingBox from "@src/components/LoadingBox/LoadingBox"; @@ -112,6 +106,9 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { IAggregatedAnalyticsOverall["overall"] | undefined >(undefined); + const [totalPatients, setTotalPatients] = useState("---"); + const [activePatients, setActivePatients] = useState("---"); + const [dashboardMenu, setDashboardMenu] = useState( DateRangeEnum.RECENT, ); @@ -169,6 +166,8 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { setReadingMenu(newDateRange); setWritingMenu(newDateRange); setTriviaMenu(newDateRange); + setTotalPatients(data.totalPatients); + setActivePatients(data.activePatients); }, [retrieveAnalytics], ); @@ -261,17 +260,19 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { triviaQuestionsCompleted: 0, } } - sessionCompletionHistory={overall?.streakHistory ?? dataBar} + sessionCompletionHistory={overall?.streakHistory ?? []} + totalPatients={totalPatients} + activePatients={activePatients} />
@@ -298,9 +299,9 @@ export default function Page({ params }: { params: { groupIds: string[] } }) {
element._id); - const aggregatedDataArray = await getAggregatedAnalytics( + const aggregatedDataObject = await getAggregatedAnalytics( usersids, range, updatedSections, ); + const aggregatedDataArray = aggregatedDataObject.analytics; + aggregatedDataArray.forEach((data) => { // for (const userdatadict of users.data) { // const data = await getAggregatedAnalytics( @@ -131,7 +133,7 @@ export const POST = APIWrapper({ groupAnalytics.overall.lastSession.triviaQuestionsCompleted += data.overall.lastSession.triviaQuestionsCompleted / usersLength; - const count = 0; + let count = 0; data.overall.streakHistory.forEach((element: DataRecord) => { const modifiedElement = { ...element }; modifiedElement.value /= usersLength; @@ -142,6 +144,7 @@ export const POST = APIWrapper({ groupAnalytics.overall!.streakHistory[count].value += modifiedElement.value; } + count += 1; }); if (data.overall.active) { @@ -421,6 +424,9 @@ export const POST = APIWrapper({ } }); + groupAnalytics.activePatients = aggregatedDataObject.activePatients; + groupAnalytics.totalPatients = aggregatedDataObject.totalPatients; + return groupAnalytics; }, }); diff --git a/src/app/api/patient/analytics/route.ts b/src/app/api/patient/analytics/route.ts index 93cd57b7..3b0ee4db 100644 --- a/src/app/api/patient/analytics/route.ts +++ b/src/app/api/patient/analytics/route.ts @@ -40,9 +40,8 @@ export const GET = APIWrapper({ const updatedSections = sections.includes(AnalyticsSectionEnum.OVERALL) ? Object.values(AnalyticsSectionEnum) : Array.from(new Set(sections)); - const data = ( - await getAggregatedAnalytics([id], range, updatedSections) - )[0]; + const data = (await getAggregatedAnalytics([id], range, updatedSections)) + .analytics[0]; return data; }, diff --git a/src/app/api/patient/auth/login/route.ts b/src/app/api/patient/auth/login/route.ts index a7700f3a..bed7a43c 100644 --- a/src/app/api/patient/auth/login/route.ts +++ b/src/app/api/patient/auth/login/route.ts @@ -39,7 +39,7 @@ export const GET = APIWrapper({ gameDetails: { active: analyticsRecord.active, streak: analyticsRecord.streak, - lastSessionsMetrics: analyticsRecord.lastSessionsMetrics, + lastSessionsMetrics: analyticsRecord.lastSessionMetrics, }, }; }, diff --git a/src/components/Dashboard/OverviewReport/OverviewReport.tsx b/src/components/Dashboard/OverviewReport/OverviewReport.tsx index 02faa851..f85dd8a4 100644 --- a/src/components/Dashboard/OverviewReport/OverviewReport.tsx +++ b/src/components/Dashboard/OverviewReport/OverviewReport.tsx @@ -30,7 +30,7 @@ export default function OverviewReport(params: Params) { > @@ -39,7 +39,7 @@ export default function OverviewReport(params: Params) { className={styles.graph} width={250} height={150} - title="New Users Over Time" + title="New Patients Over Time" hoverable data={params.activeHistory} style={{ diff --git a/src/components/Graphs/BarChart/BarChart.tsx b/src/components/Graphs/BarChart/BarChart.tsx index 6e0532b6..1f087d96 100644 --- a/src/components/Graphs/BarChart/BarChart.tsx +++ b/src/components/Graphs/BarChart/BarChart.tsx @@ -52,7 +52,6 @@ export default function BarChart({ }, hoverable = false, percentageChange = false, - highlightLargest = true, fullWidth = false, gridLines = false, info = "", @@ -96,7 +95,6 @@ export default function BarChart({ const marginRight = 25; const marginBottom = 40; const marginLeft = 35; - const [largest, setLargest] = useState(-1); const [infoPopup, setInfoPopup] = useState(false); const [popupX, setPopupX] = useState(null); const [popupY, setPopupY] = useState(null); @@ -135,24 +133,6 @@ export default function BarChart({ const yAxisFormat = yAxis?.format ? yAxis.format : (d: d3.NumberValue) => JSON.stringify(d); - function indexOfMax() { - if (newData.length === 0) { - return -1; - } - - let max = newData[0].value; - let maxIndex = 0; - - for (let i = 1; i < newData.length; i += 1) { - if (newData[i].value > max) { - maxIndex = i; - max = newData[i].value; - } - } - - return maxIndex; - } - setLargest(indexOfMax()); const svg = d3.select(windowRef.current); @@ -355,9 +335,7 @@ export default function BarChart({ H ${x0} Z `; }) - .style("fill", (d, i) => - highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", - ) + .style("fill", () => "#008AFC") .on("mouseover", (event: MouseEvent, d: DataRecord) => { tooltip.transition().duration(0).style("opacity", 1); tooltip @@ -401,9 +379,7 @@ export default function BarChart({ H ${x0} Z `; }) - .style("fill", (d, i) => - highlightLargest && largest === i ? "#FF9FB3" : "#008AFC", - ); + .style("fill", () => "#008AFC"); } } diff --git a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx index bdb23302..c8711c36 100644 --- a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx +++ b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx @@ -40,6 +40,11 @@ const GroupMathScreen = ({ style, menuState, }: InputProp) => { + const modifiedAccuracy = `${Math.round(+currentAccuracy * 100)}%`; + const modifiedTime = `${Math.round(+totalTime)} seconds`; + const modifiedQuestionsCompleted = `${Math.round(+totalQuestions)}`; + const modifiedDifficulty = `${Math.round(+currentDifficulty)}`; + return (
@@ -104,25 +109,25 @@ const GroupMathScreen = ({
diff --git a/src/components/GroupDashboard/GroupOverallDashboard/GroupOverviewReport.tsx b/src/components/GroupDashboard/GroupOverallDashboard/GroupOverviewReport.tsx index f22df670..36a46e5e 100644 --- a/src/components/GroupDashboard/GroupOverallDashboard/GroupOverviewReport.tsx +++ b/src/components/GroupDashboard/GroupOverallDashboard/GroupOverviewReport.tsx @@ -34,11 +34,17 @@ interface Params { selectedValue: DateRangeEnum, setSelectedvalue: (value: DateRangeEnum) => void, ]; - + totalPatients: number | string; + activePatients: number | string; // Need to update with the schema of the response we will get from the backend } export default function GroupOverviewReport(params: Params) { + const mathQuestionsCompleted = `${+Math.round(params.lastSession.mathQuestionsCompleted)}`; + const wordsRead = `${+Math.round(params.lastSession.wordsRead)}`; + const promptsCompleted = `${+Math.round(params.lastSession.promptsCompleted)}`; + const triviaQuestionsCompleted = `${+Math.round(params.lastSession.triviaQuestionsCompleted)}`; + return (
@@ -62,15 +68,15 @@ export default function GroupOverviewReport(params: Params) {
@@ -97,28 +103,28 @@ export default function GroupOverviewReport(params: Params) { Math} /> Reading} /> Writing} /> Trivia} /> diff --git a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.module.css b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.module.css index 0aad80e9..74164e92 100644 --- a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.module.css +++ b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.module.css @@ -38,8 +38,9 @@ .graphs { flex: 2; - display: grid; - grid-template-columns: repeat(2, 1fr); + display: inline-grid; + grid-template-columns: 1fr 1fr; + /* grid-auto-flow: column; */ gap: 15px; } diff --git a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx index 086dcbcf..d22dcc56 100644 --- a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx +++ b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx @@ -3,12 +3,7 @@ import { QuestionIcon, TimeIcon } from "@src/app/icons"; import { DateRangeEnum } from "@/common_utils/types"; import styles from "./GroupReadingScreen.module.css"; import DateSelector from "../../DateSelector/DateSelector"; -import { - StackedBarChart, - BarChart, - SmallDataBox, - LineChart, -} from "../../Graphs"; +import { BarChart, SmallDataBox, LineChart } from "../../Graphs"; const ReadingIcon = () => { return ( @@ -48,7 +43,6 @@ interface InputProp { } export default function GroupReadingScreen({ - sessionHistory, readingRate, avgPassageData, timeData, @@ -57,6 +51,8 @@ export default function GroupReadingScreen({ style, menuState, }: InputProp) { + const modifiedTime = `${Math.round(+avgTime)} seconds`; + return (
@@ -71,20 +67,6 @@ export default function GroupReadingScreen({
-
diff --git a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx index 8dcec120..7c31eb72 100644 --- a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx +++ b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx @@ -48,6 +48,10 @@ export default function GroupTriviaScreen({ style, menuState, }: InputProp) { + const modifiedAccuracy = `${Math.round(+avgAccuracy * 100)}%`; + const modifiedTime = `${Math.round(+avgTime)} seconds`; + const modifiedQuestionsCompleted = `${Math.round(+totalQuestions)}`; + return (
@@ -99,21 +103,21 @@ export default function GroupTriviaScreen({
diff --git a/src/components/GroupDashboard/GroupWritingScreen/GroupWritingScreen.tsx b/src/components/GroupDashboard/GroupWritingScreen/GroupWritingScreen.tsx index 19ff224e..3e74f1e6 100644 --- a/src/components/GroupDashboard/GroupWritingScreen/GroupWritingScreen.tsx +++ b/src/components/GroupDashboard/GroupWritingScreen/GroupWritingScreen.tsx @@ -2,7 +2,7 @@ import React from "react"; import { PromptsIcon, TimeIcon, WritingIcon } from "@src/app/icons"; import DateSelector from "@src/components/DateSelector/DateSelector"; import { DateRangeEnum } from "@/common_utils/types"; -import { StackedBarChart, SmallDataBox, BarChart } from "../../Graphs"; +import { SmallDataBox, BarChart } from "../../Graphs"; import styles from "./GroupWritingScreen.module.css"; interface InputProp { @@ -24,7 +24,6 @@ interface InputProp { } export default function GroupWritingScreen({ - sessionHistory, numCompleted, avgTimeData, totalPrompts, @@ -32,6 +31,9 @@ export default function GroupWritingScreen({ style, menuState, }: InputProp) { + const modifiedPrompts = `${Math.round(+totalPrompts)}`; + const modifiedTime = `${Math.round(+avgTime)} seconds`; + return (
@@ -46,21 +48,6 @@ export default function GroupWritingScreen({
-
From 39c1fa70f0b4dc16b6cfca737a0cd8205593a3b9 Mon Sep 17 00:00:00 2001 From: ShivaniVora <85318153+ShivaniVora@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:36:10 -0500 Subject: [PATCH 18/26] Shivani/156 bug fixes (#160) * wording changes * modal resizing to scroll * fixing overflow * fixed letter spacing --- src/app/(management-portal)/chapter/search/page.tsx | 2 +- src/app/(management-portal)/patient/search/page.tsx | 2 +- src/app/(management-portal)/volunteer/approval/page.tsx | 2 +- src/app/(management-portal)/volunteer/search/page.tsx | 2 +- src/components/AddChapterModal/AddChapterModal.module.css | 2 +- .../AddVolunteerModal/AddVolunteerModal.module.css | 2 +- src/components/ChapterVolunteerGrid/Row/Row.module.css | 3 +++ .../EditChapterModal/EditChapterModal.module.css | 2 +- src/components/NavigationPanel/NavigationPanel.module.css | 8 ++++---- src/components/PatientGrid/Row/Row.module.css | 1 + .../TransferChapterModal/TransferChapterModal.module.css | 1 + src/components/VolunteerApprovalGrid/Row/Row.module.css | 1 + src/components/VolunteerGrid/Row/Row.module.css | 2 ++ src/components/VolunteerGrid/VolunteerGrid.tsx | 4 ++-- 14 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/app/(management-portal)/chapter/search/page.tsx b/src/app/(management-portal)/chapter/search/page.tsx index 7d335dfc..2c236b00 100644 --- a/src/app/(management-portal)/chapter/search/page.tsx +++ b/src/app/(management-portal)/chapter/search/page.tsx @@ -74,7 +74,7 @@ export default function Page() {
-

Search Chapter at BEI

+

Search for BEI Chapters Here!

diff --git a/src/app/(management-portal)/patient/search/page.tsx b/src/app/(management-portal)/patient/search/page.tsx index 959f5734..49b2a269 100644 --- a/src/app/(management-portal)/patient/search/page.tsx +++ b/src/app/(management-portal)/patient/search/page.tsx @@ -122,7 +122,7 @@ export default function Page() {
-

Here are Your Patient Finds!

+

Search for Patients Here!

diff --git a/src/app/(management-portal)/volunteer/approval/page.tsx b/src/app/(management-portal)/volunteer/approval/page.tsx index d5b07317..c65e7085 100644 --- a/src/app/(management-portal)/volunteer/approval/page.tsx +++ b/src/app/(management-portal)/volunteer/approval/page.tsx @@ -125,7 +125,7 @@ export default function Page() {
-

Search for a volunteer here!

+

Approve/Deny Volunteers Here!

diff --git a/src/app/(management-portal)/volunteer/search/page.tsx b/src/app/(management-portal)/volunteer/search/page.tsx index da159fdb..e3df361e 100644 --- a/src/app/(management-portal)/volunteer/search/page.tsx +++ b/src/app/(management-portal)/volunteer/search/page.tsx @@ -119,7 +119,7 @@ export default function Page() {
-

Search for a volunteer here!

+

Search for Volunteers Here!

diff --git a/src/components/AddChapterModal/AddChapterModal.module.css b/src/components/AddChapterModal/AddChapterModal.module.css index 5dfd760c..2152d6fe 100644 --- a/src/components/AddChapterModal/AddChapterModal.module.css +++ b/src/components/AddChapterModal/AddChapterModal.module.css @@ -11,7 +11,7 @@ border-radius: var(--12, 12px); box-shadow: 14px 17px 40px 4px rgba(112, 144, 176, 0.08); font-family: var(--font-poppins); - overflow-y: visible; + overflow: scroll; position: relative; } diff --git a/src/components/AddVolunteerModal/AddVolunteerModal.module.css b/src/components/AddVolunteerModal/AddVolunteerModal.module.css index f1249e69..1336c8f1 100644 --- a/src/components/AddVolunteerModal/AddVolunteerModal.module.css +++ b/src/components/AddVolunteerModal/AddVolunteerModal.module.css @@ -11,7 +11,7 @@ border-radius: var(--12, 12px); box-shadow: 14px 17px 40px 4px rgba(112, 144, 176, 0.08); font-family: var(--font-poppins); - overflow-y: visible; + overflow: scroll; position: relative; } diff --git a/src/components/ChapterVolunteerGrid/Row/Row.module.css b/src/components/ChapterVolunteerGrid/Row/Row.module.css index 11183a71..d2dc9251 100644 --- a/src/components/ChapterVolunteerGrid/Row/Row.module.css +++ b/src/components/ChapterVolunteerGrid/Row/Row.module.css @@ -51,6 +51,8 @@ column-gap: 30px; flex-wrap: wrap; margin: 0 0 0 -10px; + padding-left: 10px; + overflow: hidden; } .ExpandedRowColumn { @@ -102,6 +104,7 @@ .Content { flex-grow: 1; text-align: left; + /* overflow: scroll; */ } .InvitePending { diff --git a/src/components/EditChapterModal/EditChapterModal.module.css b/src/components/EditChapterModal/EditChapterModal.module.css index ad251b5f..c7b10184 100644 --- a/src/components/EditChapterModal/EditChapterModal.module.css +++ b/src/components/EditChapterModal/EditChapterModal.module.css @@ -11,7 +11,7 @@ border-radius: var(--12, 12px); box-shadow: 14px 17px 40px 4px rgba(112, 144, 176, 0.08); font-family: var(--font-poppins); - overflow-y: visible; + overflow: scroll; position: relative; } diff --git a/src/components/NavigationPanel/NavigationPanel.module.css b/src/components/NavigationPanel/NavigationPanel.module.css index 623d52f7..cddf7d4e 100644 --- a/src/components/NavigationPanel/NavigationPanel.module.css +++ b/src/components/NavigationPanel/NavigationPanel.module.css @@ -48,7 +48,7 @@ font-family: var(--font-poppins); font-weight: 600; line-height: 21.04px; - letter-spacing: 1.26px; + letter-spacing: 1.15px; word-wrap: break-word; padding-bottom: 3%; } @@ -59,7 +59,7 @@ font-family: var(--font-poppins); font-weight: 600; line-height: 19px; - letter-spacing: 1.8px; + letter-spacing: 1px; word-wrap: break-word; margin-bottom: -50px; } @@ -70,7 +70,7 @@ font-family: var(--font-poppins); font-weight: 600; line-height: 19px; - letter-spacing: 1.8px; + letter-spacing: 1px; word-wrap: break-word; margin-bottom: 2.8px; } @@ -82,7 +82,7 @@ font-weight: 600; line-height: normal; line-height: 19px; - letter-spacing: 1.8px; + letter-spacing: 1px; word-wrap: break-word; } diff --git a/src/components/PatientGrid/Row/Row.module.css b/src/components/PatientGrid/Row/Row.module.css index 4be90843..2489dc8a 100644 --- a/src/components/PatientGrid/Row/Row.module.css +++ b/src/components/PatientGrid/Row/Row.module.css @@ -80,6 +80,7 @@ column-gap: 13px; flex-wrap: wrap; margin: 0 0 0 -10px; + overflow: hidden; } .ExpandedRowColumn { diff --git a/src/components/TransferChapterModal/TransferChapterModal.module.css b/src/components/TransferChapterModal/TransferChapterModal.module.css index 656e1958..25da7fdb 100644 --- a/src/components/TransferChapterModal/TransferChapterModal.module.css +++ b/src/components/TransferChapterModal/TransferChapterModal.module.css @@ -12,6 +12,7 @@ box-shadow: 14px 17px 40px 4px rgba(112, 144, 176, 0.08); font-family: var(--font-poppins); position: relative; + overflow: scroll; } .container * { diff --git a/src/components/VolunteerApprovalGrid/Row/Row.module.css b/src/components/VolunteerApprovalGrid/Row/Row.module.css index 5b920374..68578a2e 100644 --- a/src/components/VolunteerApprovalGrid/Row/Row.module.css +++ b/src/components/VolunteerApprovalGrid/Row/Row.module.css @@ -41,6 +41,7 @@ column-gap: 30px; flex-wrap: wrap; margin: 0 0 0 -10px; + overflow: hidden; } .ExpandedRowColumn { diff --git a/src/components/VolunteerGrid/Row/Row.module.css b/src/components/VolunteerGrid/Row/Row.module.css index 11183a71..bcd33f36 100644 --- a/src/components/VolunteerGrid/Row/Row.module.css +++ b/src/components/VolunteerGrid/Row/Row.module.css @@ -51,6 +51,8 @@ column-gap: 30px; flex-wrap: wrap; margin: 0 0 0 -10px; + padding-left: 10px; + overflow: hidden; } .ExpandedRowColumn { diff --git a/src/components/VolunteerGrid/VolunteerGrid.tsx b/src/components/VolunteerGrid/VolunteerGrid.tsx index f4fecbda..5b37d43a 100644 --- a/src/components/VolunteerGrid/VolunteerGrid.tsx +++ b/src/components/VolunteerGrid/VolunteerGrid.tsx @@ -43,8 +43,8 @@ function ColumnSizes() { - - + + ); From b2a39fd22bcf2d72f3ef04e3f8f9832f30d128a9 Mon Sep 17 00:00:00 2001 From: Nathan Dong <80831405+ND68@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:21:37 -0500 Subject: [PATCH 19/26] Nathan/159 fullstack address volunteer bugfixes (#162) * modal testing * accidently deleted some stuff * fixed applyDropdowns borders being cut off * implemented select entries per page for volunteers page * added entries per page parameter to patient and chapter routes, implemented select entries per page for all pagination pages * page entries info reflects total entries for volunteers page * added total entries to all pagination pages * lint & formatting --- server/mongodb/actions/Chapter.ts | 4 +- server/mongodb/actions/User.ts | 3 +- .../chapter/[name]/page.tsx | 11 +- .../chapter/search/page.tsx | 11 +- .../patient/search/page.tsx | 9 ++ .../volunteer/approval/page.tsx | 9 ++ .../volunteer/search/page.tsx | 9 ++ src/app/api/chapter/search-chapter/route.ts | 1 + src/app/api/patient/filter-patient/route.ts | 1 + src/components/ChapterGrid/ChapterGrid.tsx | 6 + .../ChapterVolunteerGrid/VolunteerGrid.tsx | 6 + .../ApplyDropdown/ApplyDropdown.module.css | 1 + .../EditChapterModal/EditChapterModal.tsx | 2 +- .../Pagination/Pagination.module.css | 23 +++ src/components/Pagination/Pagination.tsx | 147 ++++++++++++++---- src/components/PatientGrid/PatientGrid.tsx | 6 + .../VolunteerApprovalGrid.tsx | 6 + .../VolunteerGrid/VolunteerGrid.tsx | 6 + 18 files changed, 221 insertions(+), 40 deletions(-) diff --git a/server/mongodb/actions/Chapter.ts b/server/mongodb/actions/Chapter.ts index f2f6220f..5d3efa57 100644 --- a/server/mongodb/actions/Chapter.ts +++ b/server/mongodb/actions/Chapter.ts @@ -43,12 +43,14 @@ type ChapParam = { export const getChaptersFiltered = async ({ params, page, + entriesPerPage, sortParams, }: SearchRequestBody): Promise< SearchResponseBody | undefined > => { + console.log(entriesPerPage); const newPage = page ?? 0; - const numOfItems = 11; + const numOfItems = entriesPerPage || 8; const chapterParamObject = {} as ChapParam; diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index 0d2896ee..3c1dcfd9 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -152,10 +152,11 @@ export const getUsersFiltered = async ({ sortParams, searchall, onlyids, + entriesPerPage, }: SearchRequestBody): Promise< SearchResponseBody | undefined > => { - let numOfItems = 8; + let numOfItems = entriesPerPage || 8; let newpage = page; if (page === undefined) { diff --git a/src/app/(management-portal)/chapter/[name]/page.tsx b/src/app/(management-portal)/chapter/[name]/page.tsx index 2fa55b3a..c7c1482e 100644 --- a/src/app/(management-portal)/chapter/[name]/page.tsx +++ b/src/app/(management-portal)/chapter/[name]/page.tsx @@ -67,6 +67,8 @@ export default function Page({ params }: { params: { name: string } }) { const [currentPage, setCurrentPage] = useState(0); const [pageCount, setPageCount] = useState(0); const [loading, setLoading] = useState(false); + const [entriesPerPage, setEntriesPerPage] = useState(8); + const [totalEntries, setTotalEntries] = useState(0); const fetchUsers = useCallback(() => { setLoading(true); @@ -80,13 +82,15 @@ export default function Page({ params }: { params: { name: string } }) { }, page: currentPage, sortParams: sortField, + entriesPerPage, }, }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredUsers(res?.data ?? []); + setTotalEntries(res?.numRecords ?? 0); setLoading(false); }); - }, [fullName, currentPage, sortField, params.name]); + }, [fullName, currentPage, sortField, params.name, entriesPerPage]); const fetchChapterAndPresident = useCallback(() => { setLoading(true); @@ -125,7 +129,7 @@ export default function Page({ params }: { params: { name: string } }) { useEffect(() => { setCurrentPage(0); - }, [fullName, sortField]); + }, [fullName, sortField, entriesPerPage]); return (
@@ -167,6 +171,9 @@ export default function Page({ params }: { params: { name: string } }) { currentPage={currentPage} refreshUsers={fetchUsers} chapter={decodeURI(params.name)} + entriesPerPage={entriesPerPage} + setEntriesPerPage={setEntriesPerPage} + totalEntries={totalEntries} />
diff --git a/src/app/(management-portal)/chapter/search/page.tsx b/src/app/(management-portal)/chapter/search/page.tsx index 2c236b00..ae577496 100644 --- a/src/app/(management-portal)/chapter/search/page.tsx +++ b/src/app/(management-portal)/chapter/search/page.tsx @@ -34,6 +34,8 @@ export default function Page() { const [currentPage, setCurrentPage] = useState(0); const [pageCount, setPageCount] = useState(0); const [loading, setLoading] = useState(false); + const [entriesPerPage, setEntriesPerPage] = useState(8); + const [totalEntries, setTotalEntries] = useState(0); const fetchChapters = useCallback(() => { setLoading(true); @@ -46,13 +48,15 @@ export default function Page() { }, page: currentPage, sortParams: sortField, + entriesPerPage, }, }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredChapters(res?.data ?? []); + setTotalEntries(res?.numRecords ?? 0); setLoading(false); }); - }, [name, sortField, currentPage]); + }, [name, sortField, currentPage, entriesPerPage]); useEffect(() => { fetchChapters(); @@ -60,7 +64,7 @@ export default function Page() { useEffect(() => { setCurrentPage(0); - }, [name, sortField]); + }, [name, sortField, entriesPerPage]); return (
@@ -92,6 +96,9 @@ export default function Page() { setCurrentPage={setCurrentPage} pageCount={pageCount} currentPage={currentPage} + entriesPerPage={entriesPerPage} + setEntriesPerPage={setEntriesPerPage} + totalEntries={totalEntries} />
diff --git a/src/app/(management-portal)/patient/search/page.tsx b/src/app/(management-portal)/patient/search/page.tsx index 49b2a269..cc772337 100644 --- a/src/app/(management-portal)/patient/search/page.tsx +++ b/src/app/(management-portal)/patient/search/page.tsx @@ -46,6 +46,8 @@ export default function Page() { const [currentPage, setCurrentPage] = useState(0); const [pageCount, setPageCount] = useState(0); const [loading, setLoading] = useState(false); + const [entriesPerPage, setEntriesPerPage] = useState(8); + const [totalEntries, setTotalEntries] = useState(0); useEffect(() => { setLoading(true); @@ -69,10 +71,12 @@ export default function Page() { }, page: currentPage, sortParams: sortField, + entriesPerPage, }, }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredUsers(res?.data ?? []); + setTotalEntries(res?.numRecords ?? 0); setLoading(false); }); }, [ @@ -90,6 +94,7 @@ export default function Page() { secondaryNames, sortField, currentPage, + entriesPerPage, ]); useEffect(() => { @@ -108,6 +113,7 @@ export default function Page() { secondaryPhoneNumbers, secondaryNames, sortField, + entriesPerPage, ]); return ( @@ -140,6 +146,9 @@ export default function Page() { setCurrentPage={setCurrentPage} pageCount={pageCount} currentPage={currentPage} + entriesPerPage={entriesPerPage} + setEntriesPerPage={setEntriesPerPage} + totalEntries={totalEntries} />
diff --git a/src/app/(management-portal)/volunteer/approval/page.tsx b/src/app/(management-portal)/volunteer/approval/page.tsx index c65e7085..b626e90d 100644 --- a/src/app/(management-portal)/volunteer/approval/page.tsx +++ b/src/app/(management-portal)/volunteer/approval/page.tsx @@ -48,6 +48,8 @@ export default function Page() { const [currentPage, setCurrentPage] = useState(0); const [pageCount, setPageCount] = useState(0); const [loading, setLoading] = useState(false); + const [entriesPerPage, setEntriesPerPage] = useState(8); + const [totalEntries, setTotalEntries] = useState(0); const fetchUsers = useCallback(() => { setLoading(true); @@ -70,10 +72,12 @@ export default function Page() { }, page: currentPage, sortParams: sortField, + entriesPerPage, }, }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredUsers(res?.data ?? []); + setTotalEntries(res?.numRecords ?? 0); dispatch(update({ pendingApprovals: res?.numRecords ?? 0 })); setLoading(false); }); @@ -91,6 +95,7 @@ export default function Page() { currentPage, sortField, dispatch, + entriesPerPage, ]); useEffect(() => { @@ -111,6 +116,7 @@ export default function Page() { beiChapters, volunteerRoles, sortField, + entriesPerPage, ]); return ( @@ -144,6 +150,9 @@ export default function Page() { pageCount={pageCount} currentPage={currentPage} refreshUsers={fetchUsers} + entriesPerPage={entriesPerPage} + setEntriesPerPage={setEntriesPerPage} + totalEntries={totalEntries} />
diff --git a/src/app/(management-portal)/volunteer/search/page.tsx b/src/app/(management-portal)/volunteer/search/page.tsx index e3df361e..c98deee6 100644 --- a/src/app/(management-portal)/volunteer/search/page.tsx +++ b/src/app/(management-portal)/volunteer/search/page.tsx @@ -45,6 +45,8 @@ export default function Page() { const [currentPage, setCurrentPage] = useState(0); const [pageCount, setPageCount] = useState(0); const [loading, setLoading] = useState(false); + const [entriesPerPage, setEntriesPerPage] = useState(8); + const [totalEntries, setTotalEntries] = useState(0); const fetchUsers = useCallback(() => { setLoading(true); @@ -66,10 +68,12 @@ export default function Page() { }, page: currentPage, sortParams: sortField, + entriesPerPage, }, }).then((res) => { setPageCount(res?.numPages ?? 0); setFilteredUsers(res?.data ?? []); + setTotalEntries(res?.numRecords ?? 0); setLoading(false); }); }, [ @@ -85,6 +89,7 @@ export default function Page() { volunteerRoles, currentPage, sortField, + entriesPerPage, ]); useEffect(() => { @@ -105,6 +110,7 @@ export default function Page() { beiChapters, volunteerRoles, sortField, + entriesPerPage, ]); return ( @@ -138,6 +144,9 @@ export default function Page() { pageCount={pageCount} currentPage={currentPage} refreshUsers={fetchUsers} + entriesPerPage={entriesPerPage} + setEntriesPerPage={setEntriesPerPage} + totalEntries={totalEntries} />
diff --git a/src/app/api/chapter/search-chapter/route.ts b/src/app/api/chapter/search-chapter/route.ts index c3f5e4af..8ab7624c 100644 --- a/src/app/api/chapter/search-chapter/route.ts +++ b/src/app/api/chapter/search-chapter/route.ts @@ -24,6 +24,7 @@ export const POST = APIWrapper({ const chapters = await getChaptersFiltered({ params, page: reqData.page, + entriesPerPage: reqData.entriesPerPage, sortParams: reqData.sortParams, }); diff --git a/src/app/api/patient/filter-patient/route.ts b/src/app/api/patient/filter-patient/route.ts index 14d95785..7701d32e 100644 --- a/src/app/api/patient/filter-patient/route.ts +++ b/src/app/api/patient/filter-patient/route.ts @@ -27,6 +27,7 @@ export const POST = APIWrapper({ params, page: reqdata.page, sortParams: reqdata.sortParams, + entriesPerPage: reqdata.entriesPerPage, }); return users; }, diff --git a/src/components/ChapterGrid/ChapterGrid.tsx b/src/components/ChapterGrid/ChapterGrid.tsx index 78e0244e..3c8dbf94 100644 --- a/src/components/ChapterGrid/ChapterGrid.tsx +++ b/src/components/ChapterGrid/ChapterGrid.tsx @@ -19,6 +19,9 @@ interface ChapterGridProps { setCurrentPage: React.Dispatch>; pageCount: number; currentPage: number; + entriesPerPage: number; + setEntriesPerPage: (arg: number) => void; + totalEntries: number; } const columns: GridColDef[] = [ @@ -102,6 +105,9 @@ export default function ChapterGrid(params: ChapterGridProps) { setCurrentPage={params.setCurrentPage} pageCount={params.pageCount} currentPage={params.currentPage} + entriesPerPage={params.entriesPerPage} + setEntriesPerPage={params.setEntriesPerPage} + totalEntries={params.totalEntries} />
); diff --git a/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx b/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx index 2ab82359..f52a5df9 100644 --- a/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx +++ b/src/components/ChapterVolunteerGrid/VolunteerGrid.tsx @@ -22,6 +22,9 @@ interface VolunteerGridProps { currentPage: number; refreshUsers: () => void; chapter?: string; + entriesPerPage: number; + setEntriesPerPage: (arg: number) => void; + totalEntries: number; } interface HeaderProps { @@ -128,6 +131,9 @@ export default function VolunteerGrid(params: VolunteerGridProps) { setCurrentPage={params.setCurrentPage} pageCount={params.pageCount} currentPage={params.currentPage} + entriesPerPage={params.entriesPerPage} + setEntriesPerPage={params.setEntriesPerPage} + totalEntries={params.totalEntries} /> - Cancel + Clear -
- {pages.map((page, index) => { - const isCurrentPage = page === params.currentPage + 1; - return ( - <> - {Number.isNaN(page) ? ( -
- ) : ( - - )} - - ); - })} + +
+ {pages.map((page, index) => { + const isCurrentPage = page === params.currentPage + 1; + return ( + <> + {Number.isNaN(page) ? ( +
+ ) : ( + + )} + + ); + })} +
+ +
+ {} +
+

+ {" "} + Results: {params.currentPage * params.entriesPerPage + 1} -{" "} + {(params.currentPage + 1) * params.entriesPerPage > + params.totalEntries + ? params.totalEntries + : (params.currentPage + 1) * params.entriesPerPage}{" "} + of {params.totalEntries} +

+ params.setEntriesPerPage(e.target.value as number)} + showError={false} + style={{ + borderRadius: 10, + color: "#2B3674", + backgroundColor: "#E3EAFC", + border: "none", + width: "70px", + maxWidth: "90%", + textAlign: "center", + fontSize: "14px", + fontStyle: "normal", + fontWeight: 600, + lineHeight: "normal", + }} + sx={{ + "&.MuiOutlinedInput-root": { + "& fieldset": { + borderRadius: "10px", + }, + }, + }} + menuItemStyle={{ + justifyContent: "left", + fontSize: "14px", + color: "#2B3674", + fontStyle: "normal", + fontWeight: 600, + lineHeight: "normal", + }} + applyButtonStyle={{ + fontSize: "14px", + color: "#2B3674", + backgroundColor: "#E3EAFC", + fontStyle: "normal", + fontWeight: 500, + lineHeight: "normal", + }} + categoryName="Entries Per Page" + />
-
); }; diff --git a/src/components/PatientGrid/PatientGrid.tsx b/src/components/PatientGrid/PatientGrid.tsx index 30912be5..1c03d4f1 100644 --- a/src/components/PatientGrid/PatientGrid.tsx +++ b/src/components/PatientGrid/PatientGrid.tsx @@ -20,6 +20,9 @@ interface PatientGridProps { setCurrentPage: React.Dispatch>; pageCount: number; currentPage: number; + entriesPerPage: number; + setEntriesPerPage: (arg: number) => void; + totalEntries: number; } const columns: GridColDef[] = [ @@ -143,6 +146,9 @@ export default function PatientGrid(params: PatientGridProps) { setCurrentPage={params.setCurrentPage} pageCount={params.pageCount} currentPage={params.currentPage} + entriesPerPage={params.entriesPerPage} + setEntriesPerPage={params.setEntriesPerPage} + totalEntries={params.totalEntries} />
); diff --git a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx index dcdd697d..fed2ea44 100644 --- a/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx +++ b/src/components/VolunteerApprovalGrid/VolunteerApprovalGrid.tsx @@ -20,6 +20,9 @@ interface VolunteerApprovalGridProps { pageCount: number; currentPage: number; refreshUsers: () => void; + entriesPerPage: number; + setEntriesPerPage: (arg: number) => void; + totalEntries: number; } const columns: GridColDef[] = [ @@ -86,6 +89,9 @@ export default function VolunteerApprovalGrid( setCurrentPage={params.setCurrentPage} pageCount={params.pageCount} currentPage={params.currentPage} + entriesPerPage={params.entriesPerPage} + setEntriesPerPage={params.setEntriesPerPage} + totalEntries={params.totalEntries} /> void; chapter?: string; + entriesPerPage: number; + setEntriesPerPage: (arg: number) => void; + totalEntries: number; } interface HeaderProps { @@ -125,6 +128,9 @@ export default function VolunteerGrid(params: VolunteerGridProps) { setCurrentPage={params.setCurrentPage} pageCount={params.pageCount} currentPage={params.currentPage} + entriesPerPage={params.entriesPerPage} + setEntriesPerPage={params.setEntriesPerPage} + totalEntries={params.totalEntries} /> Date: Wed, 11 Dec 2024 00:18:48 -0500 Subject: [PATCH 20/26] Sophia/158 fixes (#161) * chapter list from mongo * infos * trying useeffect * trying fix * fix testing and such --------- Co-authored-by: Johannes Qian --- server/mongodb/actions/AggregatedAnalytics.ts | 6 + server/mongodb/actions/Analytics.ts | 102 +- server/mongodb/actions/Chapter.ts | 5 + server/mongodb/actions/User.ts | 4 +- server/mongodb/models/Analytics.ts | 119 +- .../patient/analytics/[id]/page.tsx | 1 + src/app/api/chapter/get-chapters/route.ts | 14 + src/app/api/patient/internal/seed/route.ts | 87 +- src/app/auth/information/page.tsx | 38 +- .../Dashboard/MathScreen/MathScreen.tsx | 10 +- .../OverallDashboard.module.scss | 6 + .../OverallDashboard/OverallDashboard.tsx | 52 +- .../Dashboard/ReadingScreen/ReadingScreen.tsx | 6 +- .../Dashboard/TriviaScreen/TriviaScreen.tsx | 3 +- .../Dashboard/WritingScreen/WritingScreen.tsx | 4 +- src/components/DateSelector/DateSelector.tsx | 2 + .../GroupMathScreen/GroupMathScreen.tsx | 5 +- .../GroupReadingScreen/GroupReadingScreen.tsx | 8 +- .../GroupTriviaScreen/GroupTriviaScreen.tsx | 5 +- .../GroupWritingScreen/GroupWritingScreen.tsx | 5 +- .../GroupSelector/GroupSelector.tsx | 2 +- .../NavigationPanel.module.css | 6 +- src/components/Pagination/Pagination.tsx | 2 - .../Search/AdvancedSearch/AdvancedSearch.tsx | 45 +- .../TransferChapterModal.module.css | 2 +- .../TransferChapterModal.tsx | 2 + .../VolunteerAdvancedSearch.tsx | 46 +- src/utils/analytics.ts | 237 ++++ src/utils/patients.ts | 1052 ++++++++++------- 29 files changed, 1283 insertions(+), 593 deletions(-) create mode 100644 src/app/api/chapter/get-chapters/route.ts create mode 100644 src/utils/analytics.ts diff --git a/server/mongodb/actions/AggregatedAnalytics.ts b/server/mongodb/actions/AggregatedAnalytics.ts index 77280bda..7ec4f2e1 100644 --- a/server/mongodb/actions/AggregatedAnalytics.ts +++ b/server/mongodb/actions/AggregatedAnalytics.ts @@ -123,6 +123,7 @@ export const getAggregatedAnalytics = async ( throw new Error("User Analytics record not found"); } + console.log("p"); const out: Partial[] = []; const lastMonday = new Date(getCurrentMonday().getDate() - 7); @@ -254,6 +255,7 @@ export const getAggregatedAnalytics = async ( } }); }); + console.log("o"); const excludedProperties = new Set([ "totalNum", "questionsCompleted", @@ -286,6 +288,7 @@ export const getAggregatedAnalytics = async ( }); }); + console.log("j"); const allDateVars = Array.from(dbDateVars).reverse(); // PADDING @@ -408,12 +411,14 @@ export const getAggregatedAnalytics = async ( } }); + console.log("sd"); const excludedProperties2 = new Set([ "totalNum", "questionsCompleted", "questionsCorrect", "avgSessionsAttempted", ]); + console.log("s2"); allDateVars.forEach((month) => { Object.entries(groupSumDict[month]).forEach(([type, monthTypeDict]) => { Object.keys(monthTypeDict).forEach((property) => { @@ -472,6 +477,7 @@ export const getAggregatedAnalytics = async ( }); }); + console.log(analyticsRecord.lastSessionMetrics); // if overshoot, remove last element // const groupSumArray = Object.values(groupSumDict) // if ((groupSumArray.length === 4 && range === "quarter") || diff --git a/server/mongodb/actions/Analytics.ts b/server/mongodb/actions/Analytics.ts index e9bb3f5f..e048f9eb 100644 --- a/server/mongodb/actions/Analytics.ts +++ b/server/mongodb/actions/Analytics.ts @@ -29,57 +29,57 @@ const checkNewDate = async (userID: string): Promise => { analytics?.lastSessionMetrics.date.getFullYear() !== today.getFullYear() ) { await Analytics.findOneAndUpdate({ userID }, [ - { - $set: { - lastSessionsMetrics: { - $cond: { - if: { $gt: [{ $size: "$lastSessionsMetrics" }, 1] }, - then: { $slice: ["$lastSessionsMetrics", 1] }, - else: "$lastSessionsMetrics", - }, - }, - }, - }, - { - $set: { - lastSessionsMetrics: { - $concatArrays: [ - [ - { - date: today, - math: { - attempted: false, - questionsAttempted: 0, - questionsCorrect: 0, - finalDifficultyScore: 0, - timePerQuestion: 0, - }, - trivia: { - attempted: false, - questionsAttempted: 0, - questionsCorrect: 0, - timePerQuestion: 0, - }, - reading: { - attempted: false, - passagesRead: 0, - timePerPassage: 0, - wordsPerMinute: 0, - skipped: true, - }, - writing: { - attempted: false, - questionsAnswered: 0, - timePerQuestion: 0, - skipped: true, - }, - }, - ], - "$lastSessionsMetrics", - ], - }, - }, - }, + // { + // $set: { + // lastSessionsMetrics: { + // $cond: { + // if: { $gt: [{ $size: "$lastSessionsMetrics" }, 1] }, + // then: { $slice: ["$lastSessionsMetrics", 1] }, + // else: "$lastSessionsMetrics", + // }, + // }, + // }, + // }, + // { + // $set: { + // lastSessionsMetrics: { + // $concatArrays: [ + // [ + // { + // date: today, + // math: { + // attempted: false, + // questionsAttempted: 0, + // questionsCorrect: 0, + // finalDifficultyScore: 0, + // timePerQuestion: 0, + // }, + // trivia: { + // attempted: false, + // questionsAttempted: 0, + // questionsCorrect: 0, + // timePerQuestion: 0, + // }, + // reading: { + // attempted: false, + // passagesRead: 0, + // timePerPassage: 0, + // wordsPerMinute: 0, + // skipped: true, + // }, + // writing: { + // attempted: false, + // questionsAnswered: 0, + // timePerQuestion: 0, + // skipped: true, + // }, + // }, + // ], + // "$lastSessionsMetrics", + // ], + // }, + // }, + // }, ]); } return null; diff --git a/server/mongodb/actions/Chapter.ts b/server/mongodb/actions/Chapter.ts index 5d3efa57..83aa04f9 100644 --- a/server/mongodb/actions/Chapter.ts +++ b/server/mongodb/actions/Chapter.ts @@ -13,6 +13,11 @@ import { ObjectId, PipelineStage, Promise } from "mongoose"; import Chapter from "../models/Chapter"; import User from "../models/User"; +export const getChapters = async (): Promise => { + const chapters = await Chapter.find(); + return chapters; +}; + export const getChapterByName = async ( name: string, ): Promise => { diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index 3c1dcfd9..90382228 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -142,7 +142,7 @@ type UParam = { "location.state"?: object; "location.city"?: object; additionalAffiliation?: object; - beiChapter?: object; + chapter?: object; "analyticsRecords.active"?: boolean; }; @@ -201,7 +201,7 @@ export const getUsersFiltered = async ({ }; } if (paramsObject.beiChapters) { - userParamsObject.beiChapter = { $in: paramsObject.beiChapters }; + userParamsObject.chapter = { $in: paramsObject.beiChapters }; } if (paramsObject.active !== undefined) { userParamsObject["analyticsRecords.active"] = paramsObject.active; diff --git a/server/mongodb/models/Analytics.ts b/server/mongodb/models/Analytics.ts index 38abe43d..c6fcb868 100644 --- a/server/mongodb/models/Analytics.ts +++ b/server/mongodb/models/Analytics.ts @@ -27,69 +27,66 @@ const AnalyticsSchema = new Schema({ }, default: [], }, - lastSessionsMetrics: { - type: [ - { - date: Date, - math: { - attempted: Boolean, - questionsAttempted: Number, - questionsCorrect: Number, - finalDifficultyScore: Number, - timePerQuestion: Number, - }, - trivia: { - attempted: Boolean, - questionsAttempted: Number, - questionsCorrect: Number, - timePerQuestion: Number, - }, - reading: { - attempted: Boolean, - passagesRead: Number, - timePerPassage: Number, - wordsPerMinute: Number, - skipped: Boolean, // should be true even if the user attempts the section but skips without completing - }, - writing: { - attempted: Boolean, - questionsAnswered: Number, - timePerQuestion: Number, - skipped: Boolean, // should be true even if the user attempts the section but skips without completing - }, + lastSessionMetrics: { + type: { + date: Date, + math: { + attempted: Boolean, + questionsAttempted: Number, + questionsCorrect: Number, + finalDifficultyScore: Number, + timePerQuestion: Number, }, - ], - default: [ - { - date: Date(), - math: { - attempted: false, - questionsAttempted: 0, - questionsCorrect: 0, - finalDifficultyScore: 0, - timePerQuestion: 0, - }, - trivia: { - attempted: false, - questionsAttempted: 0, - questionsCorrect: 0, - timePerQuestion: 0, - }, - reading: { - attempted: false, - passagesRead: 0, - timePerPassage: 0, - wordsPerMinute: 0, - skipped: true, - }, - writing: { - attempted: false, - questionsAnswered: 0, - timePerQuestion: 0, - skipped: true, - }, + trivia: { + attempted: Boolean, + questionsAttempted: Number, + questionsCorrect: Number, + timePerQuestion: Number, }, - ], + reading: { + attempted: Boolean, + passagesRead: Number, + timePerPassage: Number, + wordsPerMinute: Number, + skipped: Boolean, // should be true even if the user attempts the section but skips without completing + }, + writing: { + attempted: Boolean, + questionsAnswered: Number, + timePerQuestion: Number, + skipped: Boolean, // should be true even if the user attempts the section but skips without completing + }, + }, + + default: { + date: Date(), + math: { + attempted: false, + questionsAttempted: 0, + questionsCorrect: 0, + finalDifficultyScore: 0, + timePerQuestion: 0, + }, + trivia: { + attempted: false, + questionsAttempted: 0, + questionsCorrect: 0, + timePerQuestion: 0, + }, + reading: { + attempted: false, + passagesRead: 0, + timePerPassage: 0, + wordsPerMinute: 0, + skipped: true, + }, + writing: { + attempted: false, + questionsAnswered: 0, + timePerQuestion: 0, + skipped: true, + }, + }, }, weeklyMetrics: { type: [ diff --git a/src/app/(management-portal)/patient/analytics/[id]/page.tsx b/src/app/(management-portal)/patient/analytics/[id]/page.tsx index 8fc0e208..fa06f042 100644 --- a/src/app/(management-portal)/patient/analytics/[id]/page.tsx +++ b/src/app/(management-portal)/patient/analytics/[id]/page.tsx @@ -75,6 +75,7 @@ export default function Page({ params }: { params: { id: string } }) { const retrieveAnalytics = useCallback( async (range: DateRangeEnum, sections: AnalyticsSectionEnum[]) => { setLoading(true); + console.log(params.id); try { const data = await internalRequest({ url: "/api/patient/analytics", diff --git a/src/app/api/chapter/get-chapters/route.ts b/src/app/api/chapter/get-chapters/route.ts new file mode 100644 index 00000000..fe0551cf --- /dev/null +++ b/src/app/api/chapter/get-chapters/route.ts @@ -0,0 +1,14 @@ +import { getChapters } from "@server/mongodb/actions/Chapter"; +import APIWrapper from "@server/utils/APIWrapper"; + +export const GET = APIWrapper({ + config: { + requireToken: true, + requireAdmin: true, + }, + handler: async () => { + const chapters = await getChapters(); + + return chapters; + }, +}); diff --git a/src/app/api/patient/internal/seed/route.ts b/src/app/api/patient/internal/seed/route.ts index a1c2e7ae..988e151c 100644 --- a/src/app/api/patient/internal/seed/route.ts +++ b/src/app/api/patient/internal/seed/route.ts @@ -1,7 +1,11 @@ import APIWrapper from "@server/utils/APIWrapper"; -import User from "@server/mongodb/models/User"; -import { IUser } from "@/common_utils/types"; -import { createAnalyticsID } from "@server/mongodb/actions/Analytics"; +// import User from "@server/mongodb/models/User"; +// import { IUser } from "@/common_utils/types"; +// import { createAnalyticsID } from "@server/mongodb/actions/Analytics"; +// import { sampleUsers, sampleChapters } from "@src/utils/patients"; +// import Chapter from "@server/mongodb/models/Chapter"; +// import Analytics from "@server/mongodb/models/Analytics"; +// import { generateSampleAnalytics } from "@src/utils/analytics"; export const dynamic = "force-dynamic"; @@ -21,14 +25,69 @@ export const POST = APIWrapper({ throw new Error("User is not authorized"); } - const users = await User.find({ - role: "Nonprofit Patient", - }); - await Promise.all( - users.map(async (user: IUser) => { - await createAnalyticsID(user._id); - }), - ); + // function getRandomItem(list: T[]): T { + // const randomIndex = Math.floor(Math.random() * list.length); + // return list[randomIndex]; + // } + // async function deleteOrphanedAnalytics() { + // try { + // // Step 1: Get all valid user IDs + // const validUserIds = await User.distinct("_id"); + // console.log(validUserIds) + + // // // Step 2: Delete analytics where `userId` is not in the list of valid user IDs + // // const result = await Analytics.deleteMany({ + // // userId: { $nin: validUserIds }, // Matches documents with `userId` not in the list + // // }); + + // // console.log(`${result.deletedCount} orphaned analytics deleted.`); + // } catch (error) { + // console.error("Error deleting orphaned analytics:", error); + // } + // } + + // const rolesToSearch = ["Nonprofit Volunteer", "Nonprofit Admin", "Nonprofit Chapter President", "Nonprofit Regional Committee Member"]; + + // const volunteers = await User.find({ + // role: { $in: rolesToSearch }, + // }); + + // await Promise.all( + // volunteers.map(async (user: IUser) => { + // await User.findOneAndUpdate({ + // _id: user._id + // }, + // { + // $set: {chapter: getRandomItem(sampleChapters).name, + // role: "Nonprofit Volunteer" + // } + // } + // ) + // }), + // ); + + // const users = await User.find({ + // role: "Nonprofit Patient", + // }); + + // const sample = generateSampleAnalytics(users); + + // const newusers = []; + + // await Analytics.deleteMany(); + // await Promise.all( + // sample.map(async (sample) => { + // await Analytics.create({ + // userID: sample.userID, + // totalSessionsCompleted: sample.totalSessionsCompleted, + // active: sample.active, + // streak: sample.streak, + // lastSessionMetrics: sample.lastSessionMetrics, + // weeklyMetrics: sample.weeklyMetrics, + // }); + // }), + // ); + // for (let i = 10000; i < 11000; i++) { // await Promise.all( // sampleUsers.map(async (user: IUser) => { @@ -44,13 +103,15 @@ export const POST = APIWrapper({ // ) // } - // const users = await User.insertMany(sampleUsers); + // const users = await User.insertMany(sampleUsers); // await Promise.all( // users.map(async (user: IUser) => { // await createAnalyticsID(user._id); // }), // ); + // return null; - return null; + // const chapters = await Chapter.deleteMany(); + // const chapters = await Chapter.insertMany(sampleChapters); }, }); diff --git a/src/app/auth/information/page.tsx b/src/app/auth/information/page.tsx index 8515df18..f0e19042 100644 --- a/src/app/auth/information/page.tsx +++ b/src/app/auth/information/page.tsx @@ -1,6 +1,12 @@ "use client"; -import React, { FormEvent, MouseEvent, useState } from "react"; +import React, { + FormEvent, + MouseEvent, + useState, + useEffect, + useCallback, +} from "react"; import { Error as ErrorIcon } from "@mui/icons-material"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import { Country, State, City } from "country-state-city"; @@ -8,14 +14,14 @@ import { useRouter } from "next/navigation"; import LoadingBox from "@src/components/LoadingBox/LoadingBox"; import Modal from "@src/components/Modal/Modal"; -import { IUser, HttpMethod, Role } from "@/common_utils/types"; +import { IUser, HttpMethod, Role, IChapter } from "@/common_utils/types"; import InputField from "@src/components/InputField/InputField"; import { internalRequest } from "@src/utils/requests"; import AuthDropdown from "@src/components/Dropdown/AuthDropdown/AuthDropdown"; import { formatPhoneNumber } from "@src/utils/utils"; -import CHAPTERS from "@src/utils/chapters"; +import { DropdownOption } from "@src/components/Dropdown/Dropdown"; import styles from "./page.module.css"; export default function Page() { @@ -38,6 +44,8 @@ export default function Page() { const [chapterError, setChapterError] = useState(""); const [showGeneralError, setShowGeneralError] = useState(false); + const [chapters, setChapters] = useState[]>([]); + const [loading, setLoading] = useState(false); const router = useRouter(); @@ -155,6 +163,28 @@ export default function Page() { } }; + const loadChapters = useCallback(() => { + setLoading(true); + internalRequest({ + url: "/api/chapter/get-chapters", + method: HttpMethod.GET, + body: {}, + }).then((res) => { + const chapterDropdown = res + ? res.map((chapterVal) => ({ + value: chapterVal.name, + displayValue: chapterVal.name, + })) + : []; + setChapters(chapterDropdown); + setLoading(false); + }); + }, []); + + useEffect(() => { + loadChapters(); + }, [loadChapters]); + return (
Personal Information | Brain Exercise Initiative @@ -285,7 +315,7 @@ export default function Page() { title="Chapter" required={true} placeholder="Select Your Chaper" - options={CHAPTERS} + options={chapters} value={chapter} onChange={(e) => { setChapter(e.target.value); diff --git a/src/components/Dashboard/MathScreen/MathScreen.tsx b/src/components/Dashboard/MathScreen/MathScreen.tsx index 181e7de5..dbe040ed 100644 --- a/src/components/Dashboard/MathScreen/MathScreen.tsx +++ b/src/components/Dashboard/MathScreen/MathScreen.tsx @@ -63,7 +63,7 @@ const MathScreen = ({ title="Average Math Accuracy" hoverable={true} percentageChange={true} - info="Vidushi" + info="Number correct on first attempt divided by total" data={accuracyData} fullWidth yLabel="" @@ -76,7 +76,7 @@ const MathScreen = ({ title="Average Math Difficulty" hoverable={true} percentageChange={true} - info="Vidushi" + info="" data={difficultyData} fullWidth gridLines @@ -84,7 +84,7 @@ const MathScreen = ({
diff --git a/src/components/Dashboard/OverallDashboard/OverallDashboard.module.scss b/src/components/Dashboard/OverallDashboard/OverallDashboard.module.scss index 67c00c50..fccb17a0 100644 --- a/src/components/Dashboard/OverallDashboard/OverallDashboard.module.scss +++ b/src/components/Dashboard/OverallDashboard/OverallDashboard.module.scss @@ -20,6 +20,12 @@ font-size: 29.705px; font-style: normal; } + + .infoBox { + font-family: var(--font-poppins); + font-size: 12px; + font-style: normal; + } } .bottomContainer { diff --git a/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx b/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx index 4e0325d1..f7f1cc57 100644 --- a/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx +++ b/src/components/Dashboard/OverallDashboard/OverallDashboard.tsx @@ -1,6 +1,6 @@ import { DashboardIcon as DB, - ImportantGrayIcon as IGI, + // ImportantGrayIcon as IGI, TimeForward as TF, LastPage as PF, // CalendarIcon as COI, @@ -9,13 +9,15 @@ import { DocIcon as DI, QuestionIcon as QI, BarChartIcon, + InfoIcon, } from "@src/app/icons"; -import { CSSProperties } from "react"; +import { CSSProperties, useState, useRef, useEffect } from "react"; import { DateRangeEnum, Days } from "@/common_utils/types"; import { D3Data } from "@src/utils/types"; import Chip from "@src/components/Chip/Chip"; import ActiveIndicatorBox from "@src/components/ActiveIndicatorBox/ActiveIndicatorBox"; import DateSelector from "@src/components/DateSelector/DateSelector"; +import PopupModal from "@src/components/Graphs/PopupModal/PopupModal"; import { BarChart, SmallDataBox, WeeklyProgress } from "../../Graphs"; import styles from "./OverallDashboard.module.scss"; @@ -54,6 +56,26 @@ function formatDate(date: Date) { } export default function OverallDashboard(params: Params) { + const [infoPopup, setInfoPopup] = useState(false); + const infoButtonRef = useRef(null); + const [popupX, setPopupX] = useState(null); + const [popupY, setPopupY] = useState(null); + + useEffect(() => { + const onScroll = () => setInfoPopup(false); + // clean up code + window.removeEventListener("scroll", onScroll); + window.addEventListener("scroll", onScroll, { passive: true }); + + if (infoButtonRef.current) { + const rect: Element = infoButtonRef.current; + const newTop = rect.getBoundingClientRect().y + 30; + setPopupY(newTop); + const left = rect.getBoundingClientRect().x; + setPopupX(left); + } + }, [setPopupX, setPopupY, setInfoPopup]); + return (
@@ -74,7 +96,31 @@ export default function OverallDashboard(params: Params) { active={params.active} style={{ marginLeft: "17px", marginRight: "10px" }} /> - +
{ + setInfoPopup(true); + }} + onMouseLeave={() => { + setInfoPopup(false); + }} + ref={infoButtonRef} + style={{ position: "relative" }} + > + + +
diff --git a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx index 7bac73d0..d476fe8a 100644 --- a/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx +++ b/src/components/Dashboard/TriviaScreen/TriviaScreen.tsx @@ -71,7 +71,7 @@ export default function TriviaScreen({ title="Average Trivia Accuracy" hoverable={true} percentageChange={true} - info="Vidushi" + info="Number of self-reported correct answers" data={accuracyData} fullWidth gridLines @@ -80,6 +80,7 @@ export default function TriviaScreen({ width={325} height={175} title="Average Time Spent per Question" + info="In seconds" data={timeData} hoverable percentageChange diff --git a/src/components/Dashboard/WritingScreen/WritingScreen.tsx b/src/components/Dashboard/WritingScreen/WritingScreen.tsx index e5f4dd9e..d310a315 100644 --- a/src/components/Dashboard/WritingScreen/WritingScreen.tsx +++ b/src/components/Dashboard/WritingScreen/WritingScreen.tsx @@ -61,7 +61,6 @@ export default function WritingScreen({ // { text: "sessions completed without writing", color: "#FF9FB3" }, // { text: "sessions completed with writing", color: "#008AFC" }, // ]} - info="Some really extremely interesting information about stacked bar chart." hoverable percentageChange fullWidth @@ -76,14 +75,13 @@ export default function WritingScreen({ percentageChange fullWidth gridLines - info="Some info for testing purposes in bar chart" /> ); } diff --git a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx index c8711c36..97a85e29 100644 --- a/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx +++ b/src/components/GroupDashboard/GroupMathScreen/GroupMathScreen.tsx @@ -65,7 +65,7 @@ const GroupMathScreen = ({ title="Average Math Accuracy" hoverable={true} percentageChange={true} - info="Vidushi" + info="Number correct on first attempt divided by total" data={accuracyData} fullWidth gridLines @@ -76,7 +76,7 @@ const GroupMathScreen = ({ title="Average Math Difficulty" hoverable={true} percentageChange={true} - info="Vidushi" + info="" data={difficultyData} fullWidth gridLines @@ -96,6 +96,7 @@ const GroupMathScreen = ({ width={325} height={175} title="Average Time Spent per Question" + info="In seconds" data={timeData} hoverable percentageChange diff --git a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx index d22dcc56..44a9abfc 100644 --- a/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx +++ b/src/components/GroupDashboard/GroupReadingScreen/GroupReadingScreen.tsx @@ -69,7 +69,7 @@ export default function GroupReadingScreen({
diff --git a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx index 7c31eb72..cf723939 100644 --- a/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx +++ b/src/components/GroupDashboard/GroupTriviaScreen/GroupTriviaScreen.tsx @@ -68,11 +68,11 @@ export default function GroupTriviaScreen({
{ return [...backwardPages, ...forwardPages]; }, [params.currentPage, params.pageCount]); - console.log(pages); - const goToPreviousPage = () => { if (params.currentPage > 0) { params.setCurrentPage(params.currentPage - 1); diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index 390a21e8..30cddcfa 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -1,19 +1,31 @@ -import React, { useState, useCallback, CSSProperties, useMemo } from "react"; +import React, { + useState, + useCallback, + CSSProperties, + useMemo, + useEffect, +} from "react"; import { SelectChangeEvent } from "@mui/material"; import { Country, State, City } from "country-state-city"; import InputField from "@src/components/InputField/InputField"; -import CHAPTERS from "@src/utils/chapters"; - import { classes, transformDate, transformPhoneNumber } from "@src/utils/utils"; import { ClearTagIcon } from "@src/app/icons"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "@src/redux/rootReducer"; -import { IPatientSearchReducer } from "@/common_utils/types"; +import { + IPatientSearchReducer, + IChapter, + HttpMethod, +} from "@/common_utils/types"; +import { internalRequest } from "@src/utils/requests"; import { update, clear } from "@src/redux/reducers/patientSearchReducer"; -import Dropdown, { DropdownProps } from "../../Dropdown/Dropdown"; +import Dropdown, { + DropdownProps, + DropdownOption, +} from "../../Dropdown/Dropdown"; import styles from "./AdvancedSearch.module.css"; import "react-calendar/dist/Calendar.css"; @@ -293,6 +305,27 @@ export const AdvancedSearch = (props: UpdateParamProp) => { [dispatch], ); + const [chapters, setChapters] = useState[]>([]); + + const loadChapters = useCallback(() => { + internalRequest({ + url: "/api/chapter/get-chapters", + method: HttpMethod.GET, + }).then((res) => { + const chapterDropdown = res + ? res.map((chapter) => ({ + value: chapter.name, + displayValue: chapter.name, + })) + : []; + setChapters(chapterDropdown); + }); + }, []); + + useEffect(() => { + loadChapters(); + }, [loadChapters]); + return (
@@ -573,7 +606,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { title="BEI Chapter" dropdownProps={{ placeholder: "Select BEI Chapter", - options: CHAPTERS, + options: chapters, value: beiChapter, onChange: (e: SelectChangeEvent) => { setBeiChapter(e.target.value as string); diff --git a/src/components/TransferChapterModal/TransferChapterModal.module.css b/src/components/TransferChapterModal/TransferChapterModal.module.css index 25da7fdb..036a8641 100644 --- a/src/components/TransferChapterModal/TransferChapterModal.module.css +++ b/src/components/TransferChapterModal/TransferChapterModal.module.css @@ -12,7 +12,7 @@ box-shadow: 14px 17px 40px 4px rgba(112, 144, 176, 0.08); font-family: var(--font-poppins); position: relative; - overflow: scroll; + /* overflow: scroll; */ } .container * { diff --git a/src/components/TransferChapterModal/TransferChapterModal.tsx b/src/components/TransferChapterModal/TransferChapterModal.tsx index c6acf48e..a3690257 100644 --- a/src/components/TransferChapterModal/TransferChapterModal.tsx +++ b/src/components/TransferChapterModal/TransferChapterModal.tsx @@ -73,10 +73,12 @@ const TransferChapterModal = ({ params: { roles: [Role.NONPROFIT_VOLUNTEER], approved: [AdminApprovalStatus.APPROVED], + beiChapters: [chapter.name], }, entriesPerPage: 9999, }, }).then((res) => { + console.log(res); setVolunteers(res?.data ?? []); setLoading(false); }); diff --git a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx index 21be64b2..9c000b67 100644 --- a/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx +++ b/src/components/VolunteerSearch/VolunteerAdvancedSearch/VolunteerAdvancedSearch.tsx @@ -1,18 +1,30 @@ -import React, { useState, useCallback, CSSProperties, useMemo } from "react"; +import React, { + useState, + useCallback, + CSSProperties, + useMemo, + useEffect, +} from "react"; import { SelectChangeEvent } from "@mui/material"; import { Country, State, City } from "country-state-city"; import InputField from "@src/components/InputField/InputField"; -import CHAPTERS from "@src/utils/chapters"; - import { classes, transformDate, getLowerAdminRoles } from "@src/utils/utils"; import { ClearTagIcon } from "@src/app/icons"; +import { internalRequest } from "@src/utils/requests"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "@src/redux/rootReducer"; import { update, clear } from "@src/redux/reducers/volunteerSearchReducer"; -import { IVolunteerSearchReducer } from "@/common_utils/types"; -import Dropdown, { DropdownProps } from "../../Dropdown/Dropdown"; +import { + IVolunteerSearchReducer, + IChapter, + HttpMethod, +} from "@/common_utils/types"; +import Dropdown, { + DropdownProps, + DropdownOption, +} from "../../Dropdown/Dropdown"; import styles from "./VolunteerAdvancedSearch.module.css"; import "react-calendar/dist/Calendar.css"; import CalendarInput from "./CalendarInput"; @@ -279,6 +291,28 @@ export const VolunteerAdvancedSearch = (props: UpdateParamProp) => { [dispatch], ); + const [chapters, setChapters] = useState[]>([]); + + const loadChapters = useCallback(() => { + internalRequest({ + url: "/api/chapter/get-chapters", + method: HttpMethod.GET, + body: {}, + }).then((res) => { + const chapterDropdown = res + ? res.map((chapter) => ({ + value: chapter.name, + displayValue: chapter.name, + })) + : []; + setChapters(chapterDropdown); + }); + }, []); + + useEffect(() => { + loadChapters(); + }, [loadChapters]); + return (
@@ -520,7 +554,7 @@ export const VolunteerAdvancedSearch = (props: UpdateParamProp) => { title="BEI Chapter" dropdownProps={{ placeholder: "Select BEI Chapter", - options: CHAPTERS, + options: chapters, value: beiChapter, onChange: (e: SelectChangeEvent) => { setBeiChapter(e.target.value as string); diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts new file mode 100644 index 00000000..c931f8a3 --- /dev/null +++ b/src/utils/analytics.ts @@ -0,0 +1,237 @@ +// export const generateSampleAnalytics = (users) => { +// return Array.from({ length: users.length }, (_, i) => ({ +// userID: users[i]._id, +// totalSessionsCompleted: getRandomNumber(5, 50), +// active: getRandomBoolean(), +// streak: shuffleArray(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]).slice(0, getRandomNumber(0, 7)), +// lastSessionMetrics: +// { +// date: new Date(), +// math: { +// attempted: getRandomBoolean(), +// questionsAttempted: getRandomNumber(0, 20), +// questionsCorrect: getRandomNumber(0, 20), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// attempted: getRandomBoolean(), +// questionsAttempted: getRandomNumber(0, 20), +// questionsCorrect: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// attempted: getRandomBoolean(), +// passagesRead: getRandomNumber(0, 5), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// skipped: getRandomBoolean(), +// }, +// writing: { +// attempted: getRandomBoolean(), +// questionsAnswered: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 120), +// skipped: getRandomBoolean(), +// }, +// }, +// weeklyMetrics: [ +// { +// date: new Date(new Date().setDate(new Date().getDate() - 7)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// { +// date: new Date(new Date().setDate(new Date().getDate() - 14)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// { +// date: new Date(new Date().setDate(new Date().getDate() - 21)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// { +// date: new Date(new Date().setDate(new Date().getDate() - 28)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// { +// date: new Date(new Date().setDate(new Date().getDate() - 35)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// { +// date: new Date(new Date().setDate(new Date().getDate() - 42)), +// sessionsCompleted: getRandomNumber(0, 10), +// streakLength: getRandomNumber(0, 7), +// active: getRandomBoolean(), +// math: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// finalDifficultyScore: getRandomNumber(0, 10), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// trivia: { +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAttempted: getRandomNumber(0, 50), +// questionsCorrect: getRandomNumber(0, 50), +// timePerQuestion: getRandomNumber(0, 60), +// }, +// reading: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// passagesRead: getRandomNumber(0, 10), +// timePerPassage: getRandomNumber(0, 300), +// wordsPerMinute: getRandomNumber(0, 300), +// }, +// writing: { +// sessionsAttempted: getRandomNumber(0, 5), +// sessionsCompleted: getRandomNumber(0, 5), +// questionsAnswered: getRandomNumber(0, 20), +// timePerQuestion: getRandomNumber(0, 120), +// }, +// }, +// ], +// })); +// }; + +// Get the generated data +// const sampleData = generateSampleAnalytics(); + +// export const generateSampleAnalytics diff --git a/src/utils/patients.ts b/src/utils/patients.ts index d30008c5..94a9fbed 100644 --- a/src/utils/patients.ts +++ b/src/utils/patients.ts @@ -1,426 +1,640 @@ -import { - AdminApprovalStatus, - IPatientTableEntry, - IUser, - Role, -} from "@/common_utils/types"; +// import { +// AdminApprovalStatus, +// IChapter, +// IPatientTableEntry, +// IUser, +// Role, +// } from "@/common_utils/types"; -export const sampleTableEntries: IPatientTableEntry[] = [ - { - _id: "1", - firstName: "John", - lastName: "Doe", - birthDate: new Date("1990-01-01").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Jane Doe", - secondaryContactPhone: "5552221515", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "john.doe@example.com", - chapter: "Georgia Institute of Technology", - location: { - country: "USA", - state: "California", - city: "Los Angeles", - }, - startDate: new Date("2020-05-05").toLocaleDateString(), - }, - { - _id: "2", - firstName: "Jane", - lastName: "Smith", - birthDate: new Date("1992-02-02").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Ella Smith", - secondaryContactPhone: "5553347890", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "jane.smith@example.com", - chapter: "Toronto Central", - location: { - country: "Canada", - state: "Ontario", - city: "Toronto", - }, - startDate: new Date("2021-01-10").toLocaleDateString(), - }, - { - _id: "3", - firstName: "Alice", - lastName: "Johnson", - birthDate: new Date("1985-03-03").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Michael Johnson", - secondaryContactPhone: "5554567890", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: false, - email: "alice.johnson@example.com", - chapter: "London Health Sciences Center", - location: { - country: "UK", - state: "England", - city: "London", - }, - startDate: new Date("2021-02-20").toLocaleDateString(), - }, - { - _id: "4", - firstName: "Bob", - lastName: "Brown", - birthDate: new Date("1987-04-04").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Linda Brown", - secondaryContactPhone: "5555678901", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "bob.brown@example.com", - chapter: "Sydney Medical Center", - location: { - country: "Australia", - state: "New South Wales", - city: "Sydney", - }, - startDate: new Date("2021-03-15").toLocaleDateString(), - }, - { - _id: "5", - firstName: "Charlie", - lastName: "Davis", - birthDate: new Date("1995-05-05").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Sarah Davis", - secondaryContactPhone: "5556789012", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: false, - email: "charlie.davis@example.com", - chapter: "Mumbai Health Forum", - location: { - country: "India", - state: "Maharashtra", - city: "Mumbai", - }, - startDate: new Date("2021-04-22").toLocaleDateString(), - }, - { - _id: "6", - firstName: "Diana", - lastName: "Evans", - birthDate: new Date("1980-06-06").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Peter Evans", - secondaryContactPhone: "5557890123", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "diana.evans@example.com", - chapter: "Rio de Janeiro Health Institute", - location: { - country: "Brazil", - state: "Rio de Janeiro", - city: "Rio de Janeiro", - }, - startDate: new Date("2021-05-30").toLocaleDateString(), - }, - { - _id: "7", - firstName: "Evan", - lastName: "Foster", - birthDate: new Date("1988-07-07").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Julia Foster", - secondaryContactPhone: "5558901234", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: false, - email: "evan.foster@example.com", - chapter: "Bavarian Medical Society", - location: { - country: "Germany", - state: "Bavaria", - city: "Munich", - }, - startDate: new Date("2021-07-14").toLocaleDateString(), - }, - { - _id: "8", - firstName: "Fiona", - lastName: "Green", - birthDate: new Date("1993-08-08").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Mark Green", - secondaryContactPhone: "5559012345", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "fiona.green@example.com", - chapter: "Johannesburg Health Chapter", - location: { - country: "South Africa", - state: "Gauteng", - city: "Johannesburg", - }, - startDate: new Date("2021-08-28").toLocaleDateString(), - }, - { - _id: "9", - firstName: "George", - lastName: "Harris", - birthDate: new Date("1975-09-09").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Lucy Harris", - secondaryContactPhone: "5550123456", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: true, - email: "george.harris@example.com", - chapter: "Tokyo Medical Association", - location: { - country: "Japan", - state: "Tokyo", - city: "Tokyo", - }, - startDate: new Date("2021-09-12").toLocaleDateString(), - }, - { - _id: "10", - firstName: "Hannah", - lastName: "Adams", - birthDate: new Date("1997-10-10").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Oliver Adams", - secondaryContactPhone: "5551234567", - additionalAffiliation: "Patient visits Florida chapter every Tuesday", - }, - active: false, - email: "hannah.adams@example.com", - chapter: "Wellington Health Services", - location: { - country: "New Zealand", - state: "Wellington", - city: "Wellington", - }, - startDate: new Date("2021-10-05").toLocaleDateString(), - }, - { - _id: "11", - firstName: "David", - lastName: "Johnson", - birthDate: new Date("1993-08-12").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Emma Johnson", - secondaryContactPhone: "5558901234", - additionalAffiliation: "Patient attends health workshops regularly", - }, - active: true, - email: "david.johnson@example.com", - chapter: "New York Medical Society", - location: { - country: "USA", - state: "New York", - city: "New York City", - }, +// export const sampleTableEntries: IPatientTableEntry[] = [ +// { +// _id: "1", +// firstName: "John", +// lastName: "Doe", +// birthDate: new Date("1990-01-01").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Jane Doe", +// secondaryContactPhone: "5552221515", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "john.doe@example.com", +// chapter: "Georgia Tech", +// location: { +// country: "USA", +// state: "California", +// city: "Los Angeles", +// }, +// startDate: new Date("2020-05-05").toLocaleDateString(), +// }, +// { +// _id: "2", +// firstName: "Jane", +// lastName: "Smith", +// birthDate: new Date("1992-02-02").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Ella Smith", +// secondaryContactPhone: "5553347890", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "jane.smith@example.com", +// chapter: "Georgia Tech", +// location: { +// country: "Canada", +// state: "Ontario", +// city: "Toronto", +// }, +// startDate: new Date("2021-01-10").toLocaleDateString(), +// }, +// { +// _id: "3", +// firstName: "Alice", +// lastName: "Johnson", +// birthDate: new Date("1985-03-03").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Michael Johnson", +// secondaryContactPhone: "5554567890", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: false, +// email: "alice.johnson@example.com", +// chapter: "Georgia Tech", +// location: { +// country: "UK", +// state: "England", +// city: "London", +// }, +// startDate: new Date("2021-02-20").toLocaleDateString(), +// }, +// { +// _id: "4", +// firstName: "Bob", +// lastName: "Brown", +// birthDate: new Date("1987-04-04").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Linda Brown", +// secondaryContactPhone: "5555678901", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "bob.brown@example.com", +// chapter: "Johns Hopkins University", +// location: { +// country: "Australia", +// state: "New South Wales", +// city: "Sydney", +// }, +// startDate: new Date("2021-03-15").toLocaleDateString(), +// }, +// { +// _id: "5", +// firstName: "Charlie", +// lastName: "Davis", +// birthDate: new Date("1995-05-05").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Sarah Davis", +// secondaryContactPhone: "5556789012", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: false, +// email: "charlie.davis@example.com", +// chapter: "University of Pennsylvania", +// location: { +// country: "India", +// state: "Maharashtra", +// city: "Mumbai", +// }, +// startDate: new Date("2021-04-22").toLocaleDateString(), +// }, +// { +// _id: "6", +// firstName: "Diana", +// lastName: "Evans", +// birthDate: new Date("1980-06-06").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Peter Evans", +// secondaryContactPhone: "5557890123", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "diana.evans@example.com", +// chapter: "Harvard University", +// location: { +// country: "Brazil", +// state: "Rio de Janeiro", +// city: "Rio de Janeiro", +// }, +// startDate: new Date("2021-05-30").toLocaleDateString(), +// }, +// { +// _id: "7", +// firstName: "Evan", +// lastName: "Foster", +// birthDate: new Date("1988-07-07").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Julia Foster", +// secondaryContactPhone: "5558901234", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: false, +// email: "evan.foster@example.com", +// chapter: "Columbia University", +// location: { +// country: "Germany", +// state: "Bavaria", +// city: "Munich", +// }, +// startDate: new Date("2021-07-14").toLocaleDateString(), +// }, +// { +// _id: "8", +// firstName: "Fiona", +// lastName: "Green", +// birthDate: new Date("1993-08-08").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Mark Green", +// secondaryContactPhone: "5559012345", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "fiona.green@example.com", +// chapter: "Maryland", +// location: { +// country: "South Africa", +// state: "Gauteng", +// city: "Johannesburg", +// }, +// startDate: new Date("2021-08-28").toLocaleDateString(), +// }, +// { +// _id: "9", +// firstName: "George", +// lastName: "Harris", +// birthDate: new Date("1975-09-09").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Lucy Harris", +// secondaryContactPhone: "5550123456", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: true, +// email: "george.harris@example.com", +// chapter: "Maryland", +// location: { +// country: "Japan", +// state: "Tokyo", +// city: "Tokyo", +// }, +// startDate: new Date("2021-09-12").toLocaleDateString(), +// }, +// { +// _id: "10", +// firstName: "Hannah", +// lastName: "Adams", +// birthDate: new Date("1997-10-10").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Oliver Adams", +// secondaryContactPhone: "5551234567", +// additionalAffiliation: "Patient visits Florida chapter every Tuesday", +// }, +// active: false, +// email: "hannah.adams@example.com", +// chapter: "Maryland", +// location: { +// country: "New Zealand", +// state: "Wellington", +// city: "Wellington", +// }, +// startDate: new Date("2021-10-05").toLocaleDateString(), +// }, +// { +// _id: "11", +// firstName: "David", +// lastName: "Johnson", +// birthDate: new Date("1993-08-12").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Emma Johnson", +// secondaryContactPhone: "5558901234", +// additionalAffiliation: "Patient attends health workshops regularly", +// }, +// active: true, +// email: "Maryland", +// chapter: "New York Medical Society", +// location: { +// country: "USA", +// state: "New York", +// city: "New York City", +// }, - startDate: new Date("2021-05-20").toLocaleDateString(), - }, - { - _id: "12", - firstName: "Sophie", - lastName: "Brown", - birthDate: new Date("1986-11-15").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Ryan Brown", - secondaryContactPhone: "5552345678", - additionalAffiliation: "Patient advocates for mental health awareness", - }, - active: false, - email: "sophie.brown@example.com", - chapter: "Paris Health Institute", - location: { - country: "France", - state: "Île-de-France", - city: "Paris", - }, - startDate: new Date("2021-06-10").toLocaleDateString(), - }, - { - _id: "13", - firstName: "Oliver", - lastName: "White", - birthDate: new Date("1990-03-25").toLocaleDateString(), - patientDetails: { - secondaryContactName: "Ava White", - secondaryContactPhone: "5553456789", - additionalAffiliation: "Patient volunteers at local health clinics", - }, - active: true, - email: "oliver.white@example.com", - chapter: "Berlin Medical Association", - location: { - country: "Germany", - state: "Berlin", - city: "Berlin", - }, - startDate: new Date("2021-07-15").toLocaleDateString(), - }, -]; +// startDate: new Date("2021-05-20").toLocaleDateString(), +// }, +// { +// _id: "12", +// firstName: "Sophie", +// lastName: "Brown", +// birthDate: new Date("1986-11-15").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Ryan Brown", +// secondaryContactPhone: "5552345678", +// additionalAffiliation: "Patient advocates for mental health awareness", +// }, +// active: false, +// email: "sophie.brown@example.com", +// chapter: "University of Notre Dame", +// location: { +// country: "France", +// state: "Île-de-France", +// city: "Paris", +// }, +// startDate: new Date("2021-06-10").toLocaleDateString(), +// }, +// { +// _id: "13", +// firstName: "Oliver", +// lastName: "White", +// birthDate: new Date("1990-03-25").toLocaleDateString(), +// patientDetails: { +// secondaryContactName: "Ava White", +// secondaryContactPhone: "5553456789", +// additionalAffiliation: "Patient volunteers at local health clinics", +// }, +// active: true, +// email: "oliver.white@example.com", +// chapter: "Michigan State University", +// location: { +// country: "Germany", +// state: "Berlin", +// city: "Berlin", +// }, +// startDate: new Date("2021-07-15").toLocaleDateString(), +// }, +// ]; -export const dataLine = [ - { - interval: "Aug 17", - value: 0.4, - }, - { - interval: "Aug 17", - value: 0.2, - }, - { - interval: "Aug 17", - value: 0.65, - }, - { - interval: "Aug 17", - value: 0.25, - }, - { - interval: "Aug 17", - value: 0.7, - }, - { - interval: "Aug 17", - value: 0.55, - }, -]; +// const sampleChapterEntries = [ +// { +// name: "Johns Hopkins University", +// chapterPresident: "647f15c2c9a1b01234567890", +// patients: 50, +// activeVolunteers: 15, +// inactiveVolunteers: 5, +// yearFounded: 2010, +// location: { country: "USA", state: "Maryland", city: "Baltimore" }, +// }, +// { +// name: "NIH Post-bac in Rockville, Maryland", +// chapterPresident: "647f15c2c9a1b01234567891", +// patients: 30, +// activeVolunteers: 10, +// inactiveVolunteers: 2, +// yearFounded: 2015, +// location: { country: "USA", state: "Maryland", city: "Rockville" }, +// }, +// { +// name: "University of Maryland", +// chapterPresident: "647f15c2c9a1b01234567892", +// patients: 40, +// activeVolunteers: 20, +// inactiveVolunteers: 4, +// yearFounded: 2005, +// location: { country: "USA", state: "Maryland", city: "College Park" }, +// }, +// { +// name: "Washington and Lee University", +// chapterPresident: "647f15c2c9a1b01234567893", +// patients: 25, +// activeVolunteers: 8, +// inactiveVolunteers: 1, +// yearFounded: 2000, +// location: { country: "USA", state: "Virginia", city: "Lexington" }, +// }, +// { +// name: "Purdue University", +// chapterPresident: "647f15c2c9a1b01234567894", +// patients: 60, +// activeVolunteers: 25, +// inactiveVolunteers: 5, +// yearFounded: 2008, +// location: { country: "USA", state: "Indiana", city: "West Lafayette" }, +// }, +// { +// name: "University of Notre Dame", +// chapterPresident: "647f15c2c9a1b01234567895", +// patients: 45, +// activeVolunteers: 18, +// inactiveVolunteers: 3, +// yearFounded: 2003, +// location: { country: "USA", state: "Indiana", city: "Notre Dame" }, +// }, +// { +// name: "Indiana University Bloomington", +// chapterPresident: "647f15c2c9a1b01234567896", +// patients: 35, +// activeVolunteers: 12, +// inactiveVolunteers: 4, +// yearFounded: 2012, +// location: { country: "USA", state: "Indiana", city: "Bloomington" }, +// }, +// { +// name: "University of Michigan", +// chapterPresident: "647f15c2c9a1b01234567897", +// patients: 55, +// activeVolunteers: 22, +// inactiveVolunteers: 6, +// yearFounded: 2011, +// location: { country: "USA", state: "Michigan", city: "Ann Arbor" }, +// }, +// { +// name: "Michigan State University", +// chapterPresident: "647f15c2c9a1b01234567898", +// patients: 50, +// activeVolunteers: 20, +// inactiveVolunteers: 5, +// yearFounded: 2007, +// location: { country: "USA", state: "Michigan", city: "East Lansing" }, +// }, +// { +// name: "Oakland University", +// chapterPresident: "647f15c2c9a1b01234567899", +// patients: 30, +// activeVolunteers: 10, +// inactiveVolunteers: 3, +// yearFounded: 2018, +// location: { country: "USA", state: "Michigan", city: "Rochester" }, +// }, +// { +// name: "Case Western Reserve University", +// chapterPresident: "647f15c2c9a1b0123456789a", +// patients: 45, +// activeVolunteers: 17, +// inactiveVolunteers: 4, +// yearFounded: 2010, +// location: { country: "USA", state: "Ohio", city: "Cleveland" }, +// }, +// { +// name: "Wake Forest", +// chapterPresident: "647f15c2c9a1b0123456789b", +// patients: 25, +// activeVolunteers: 9, +// inactiveVolunteers: 2, +// yearFounded: 2015, +// location: { +// country: "USA", +// state: "North Carolina", +// city: "Winston-Salem", +// }, +// }, +// { +// name: "Augusta University", +// chapterPresident: "647f15c2c9a1b0123456789c", +// patients: 20, +// activeVolunteers: 7, +// inactiveVolunteers: 2, +// yearFounded: 2017, +// location: { country: "USA", state: "Georgia", city: "Augusta" }, +// }, +// { +// name: "Georgia Tech", +// chapterPresident: "647f15c2c9a1b0123456789d", +// patients: 75, +// activeVolunteers: 30, +// inactiveVolunteers: 8, +// yearFounded: 2009, +// location: { country: "USA", state: "Georgia", city: "Atlanta" }, +// }, +// { +// name: "University of Tennessee", +// chapterPresident: "647f15c2c9a1b0123456789e", +// patients: 35, +// activeVolunteers: 15, +// inactiveVolunteers: 4, +// yearFounded: 2013, +// location: { country: "USA", state: "Tennessee", city: "Knoxville" }, +// }, +// { +// name: "University of Georgia", +// chapterPresident: "647f15c2c9a1b0123456789f", +// patients: 40, +// activeVolunteers: 16, +// inactiveVolunteers: 5, +// yearFounded: 2006, +// location: { country: "USA", state: "Georgia", city: "Athens" }, +// }, +// { +// name: "Emory University", +// chapterPresident: "647f15c2c9a1b012345678a0", +// patients: 50, +// activeVolunteers: 20, +// inactiveVolunteers: 6, +// yearFounded: 2004, +// location: { country: "USA", state: "Georgia", city: "Atlanta" }, +// }, +// { +// name: "Denmark High School", +// chapterPresident: "647f15c2c9a1b012345678a1", +// patients: 15, +// activeVolunteers: 5, +// inactiveVolunteers: 1, +// yearFounded: 2020, +// location: { country: "USA", state: "Georgia", city: "Alpharetta" }, +// }, +// { +// name: "Florida State University", +// chapterPresident: "647f15c2c9a1b012345678a2", +// patients: 45, +// activeVolunteers: 18, +// inactiveVolunteers: 4, +// yearFounded: 2016, +// location: { country: "USA", state: "Florida", city: "Tallahassee" }, +// }, +// { +// name: "University of Miami", +// chapterPresident: "647f15c2c9a1b012345678a3", +// patients: 50, +// activeVolunteers: 22, +// inactiveVolunteers: 5, +// yearFounded: 2011, +// location: { country: "USA", state: "Florida", city: "Miami" }, +// }, +// { +// name: "Florida Institute of Technology", +// chapterPresident: "647f15c2c9a1b012345678a4", +// patients: 35, +// activeVolunteers: 12, +// inactiveVolunteers: 3, +// yearFounded: 2018, +// location: { country: "USA", state: "Florida", city: "Melbourne" }, +// }, +// ]; -export const dataBar = [ - { - interval: "Aug 17", - value: 0, - }, - { - interval: "Aug 17", - value: 2, - }, - { - interval: "Aug 17", - value: 1, - }, - { - interval: "Aug 17", - value: 6, - }, - { - interval: "Aug 17", - value: 5, - }, - { - interval: "Aug 17", - value: 7, - }, -]; -export const dataStacked = [ - { - interval: "Aug 17", - stackedValue: 0.3, - value: 0.5, - }, - { - interval: "Aug 17", - stackedValue: 0.2, - value: 0.6, - }, - { - interval: "Aug 17", - stackedValue: 0.2, - value: 0.7, - }, - { - interval: "Aug 17", - stackedValue: 0.5, - value: 0.6, - }, - { - interval: "Aug 17", - stackedValue: 0.2, - value: 0.5, - }, - { - interval: "Aug 17", - stackedValue: 0.0, - value: 0.8, - }, -]; +// export const dataLine = [ +// { +// interval: "Aug 17", +// value: 0.4, +// }, +// { +// interval: "Aug 17", +// value: 0.2, +// }, +// { +// interval: "Aug 17", +// value: 0.65, +// }, +// { +// interval: "Aug 17", +// value: 0.25, +// }, +// { +// interval: "Aug 17", +// value: 0.7, +// }, +// { +// interval: "Aug 17", +// value: 0.55, +// }, +// ]; -export const numberOfQuestionData = [ - { - interval: "Aug 17", - value: 25, - }, - { - interval: "Aug 17", - value: 30, - }, - { - interval: "Aug 17", - value: 15, - }, - { - interval: "Aug 17", - value: 10, - }, - { - interval: "Aug 17", - value: 10, - }, - { - interval: "10/25", - value: 8, - }, - { - interval: "11/01", - value: 8, - }, -]; +// export const dataBar = [ +// { +// interval: "Aug 17", +// value: 0, +// }, +// { +// interval: "Aug 17", +// value: 2, +// }, +// { +// interval: "Aug 17", +// value: 1, +// }, +// { +// interval: "Aug 17", +// value: 6, +// }, +// { +// interval: "Aug 17", +// value: 5, +// }, +// { +// interval: "Aug 17", +// value: 7, +// }, +// ]; +// export const dataStacked = [ +// { +// interval: "Aug 17", +// stackedValue: 0.3, +// value: 0.5, +// }, +// { +// interval: "Aug 17", +// stackedValue: 0.2, +// value: 0.6, +// }, +// { +// interval: "Aug 17", +// stackedValue: 0.2, +// value: 0.7, +// }, +// { +// interval: "Aug 17", +// stackedValue: 0.5, +// value: 0.6, +// }, +// { +// interval: "Aug 17", +// stackedValue: 0.2, +// value: 0.5, +// }, +// { +// interval: "Aug 17", +// stackedValue: 0.0, +// value: 0.8, +// }, +// ]; -// export const sampleUsers: IUser[] = [].concat( -// ...Array(750).fill(null).map((_, fillIndex) => -// sampleTableEntries.map((entry, entryIndex) => ({ -// name: entry.name, -// email: `${entry.email}${fillIndex + 25}`, // Concatenate fillIndex and entryIndex -// phoneNumber: "1234567890", -// patientDetails: entry.patientDetails, -// adminDetails: { active: entry.active }, -// chapter: entry.chapter, -// location: entry.location, -// signedUp: true, -// verified: true, -// approved: AdminApprovalStatus.APPROVED, -// role: Role.NONPROFIT_PATIENT, -// })) -// ) -// ); +// export const numberOfQuestionData = [ +// { +// interval: "Aug 17", +// value: 25, +// }, +// { +// interval: "Aug 17", +// value: 30, +// }, +// { +// interval: "Aug 17", +// value: 15, +// }, +// { +// interval: "Aug 17", +// value: 10, +// }, +// { +// interval: "Aug 17", +// value: 10, +// }, +// { +// interval: "10/25", +// value: 8, +// }, +// { +// interval: "11/01", +// value: 8, +// }, +// ]; -export const sampleUsers: IUser[] = sampleTableEntries.map((entry) => { - return { - _id: entry._id, - firstName: entry.firstName, - lastName: entry.lastName, - email: entry.email, - phoneNumber: "1234567890", - startDate: entry.startDate, - birthDate: entry.birthDate, - patientDetails: entry.patientDetails, - adminDetails: { active: entry.active }, - chapter: entry.chapter, - location: entry.location, - signedUp: true, - verified: true, - approved: AdminApprovalStatus.APPROVED, - role: Role.NONPROFIT_PATIENT, - imageLink: "", - }; -}); +// // export const sampleUsers: IUser[] = [].concat( +// // ...Array(750).fill(null).map((_, fillIndex) => +// // sampleTableEntries.map((entry, entryIndex) => ({ +// // name: entry.name, +// // email: `${entry.email}${fillIndex + 25}`, // Concatenate fillIndex and entryIndex +// // phoneNumber: "1234567890", +// // patientDetails: entry.patientDetails, +// // adminDetails: { active: entry.active }, +// // chapter: entry.chapter, +// // location: entry.location, +// // signedUp: true, +// // verified: true, +// // approved: AdminApprovalStatus.APPROVED, +// // role: Role.NONPROFIT_PATIENT, +// // })) +// // ) +// // ); + +// // export const sampleUsers: IUser[] = sampleTableEntries.map((entry) => { +// // return { +// // // _id: entry._id, +// // firstName: entry.firstName, +// // lastName: entry.lastName, +// // email: entry.email, +// // phoneNumber: "1234567890", +// // startDate: entry.startDate, +// // birthDate: entry.birthDate, +// // patientDetails: entry.patientDetails, +// // adminDetails: { active: entry.active }, +// // chapter: entry.chapter, +// // location: entry.location, +// // signedUp: true, +// // verified: true, +// // approved: AdminApprovalStatus.APPROVED, +// // role: Role.NONPROFIT_PATIENT, +// // imageLink: "", +// // }; +// // }); + +// // export const sampleChapters: IChapter[] = sampleChapterEntries.map((entry) => { +// // return { +// // name: entry.name, // Assuming this is provided in sampleChapterEntries +// // chapterPresident: entry.chapterPresident || "647f15c2c9a1b01234567890", // Default ObjectId if not provided +// // patients: entry.patients || 0, // Default to 0 if not provided +// // activeVolunteers: entry.activeVolunteers || 0, // Default to 0 if not provided +// // inactiveVolunteers: entry.inactiveVolunteers || 0, // Default to 0 if not provided +// // yearFounded: entry.yearFounded || new Date().getFullYear(), // Default to the current year +// // location: entry.location || { +// // // Default to an empty location object +// // country: "", +// // state: "", +// // city: "", +// // }, +// // }; +// // }); From bdc33873cd40a82ab8cf5a89561e294998778b38 Mon Sep 17 00:00:00 2001 From: andyvo2004 <124488343+andyvo2004@users.noreply.github.com> Date: Wed, 11 Dec 2024 00:19:36 -0500 Subject: [PATCH 21/26] add edit-user endpoint (#164) --- server/mongodb/actions/User.ts | 20 +++++++++ src/app/api/patient/edit-patient/route.ts | 51 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/app/api/patient/edit-patient/route.ts diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index 90382228..efabe7e9 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -86,6 +86,26 @@ export const patientSignUp = async ( return result; }; +export const patientEdit = async ( + data: Omit< + IUser, + "chapter" | "location" | "patientDetails" | "new" | "signedUp" | "role" + >, +): Promise => { + const result = await User.findOneAndUpdate( + { email: data.email }, + { + $set: { + firstName: data.firstName, + lastName: data.lastName, + phoneNumber: data.phoneNumber, + birthDate: new Date(data.birthDate), + }, + }, + ); + return result; +}; + export const volunteerSignUp = async ( email: string, firstName: string, diff --git a/src/app/api/patient/edit-patient/route.ts b/src/app/api/patient/edit-patient/route.ts new file mode 100644 index 00000000..e3ddebcd --- /dev/null +++ b/src/app/api/patient/edit-patient/route.ts @@ -0,0 +1,51 @@ +import { getUserByEmail, patientEdit } from "@server/mongodb/actions/User"; +import APIWrapper from "@server/utils/APIWrapper"; +import { IUser } from "@/common_utils/types"; + +interface EditData { + email: string; + firstName: string; + lastName: string; + phoneNumber: string; + birthDate: string; +} + +export const POST = APIWrapper({ + config: { + requireToken: false, + }, + handler: async (req) => { + const editData: EditData = (await req.json()) as EditData; + + if (!editData) { + throw new Error("Missing request data"); + } + + if ( + editData.birthDate === undefined || + editData.email === undefined || + editData.firstName === undefined || + editData.lastName === undefined || + editData.phoneNumber === undefined + ) { + throw new Error("Missing parameter(s)"); + } + + const user = await getUserByEmail(editData.email); + if (!user) { + throw new Error("User not found."); + } + + const newSignUp = await patientEdit({ + email: editData.email, + firstName: editData.firstName, + lastName: editData.lastName, + phoneNumber: editData.phoneNumber, + birthDate: editData.birthDate, + } as Omit< + IUser, + "chapter" | "location" | "patientDetails" | "new" | "signedUp" | "role" + >); + return newSignUp; + }, +}); From ef28d366f3563afd4ec25f3e0fc60bf9ed38152c Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 31 Jan 2025 14:57:53 -0500 Subject: [PATCH 22/26] conditional state and city filter text --- src/components/Search/AdvancedSearch/AdvancedSearch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index 30cddcfa..43dffd3a 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -562,7 +562,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { ) => { @@ -584,7 +584,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { ) => { From 328e0a4c60d661699eea3be9a9c80a840d0001f8 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 31 Jan 2025 15:22:21 -0500 Subject: [PATCH 23/26] fixed additionalAffiliations filter, added partial matches and made search case-insensitive --- server/mongodb/actions/User.ts | 8 +++++--- src/components/Search/AdvancedSearch/AdvancedSearch.tsx | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index efabe7e9..d2bb55d3 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -161,7 +161,7 @@ type UParam = { "location.country"?: object; "location.state"?: object; "location.city"?: object; - additionalAffiliation?: object; + "patientDetails.additionalAffiliation"?: object; chapter?: object; "analyticsRecords.active"?: boolean; }; @@ -216,8 +216,10 @@ export const getUsersFiltered = async ({ userParamsObject["location.city"] = { $in: paramsObject.cities }; } if (paramsObject.additionalAffiliations) { - userParamsObject.additionalAffiliation = { - $in: paramsObject.additionalAffiliations, + userParamsObject["patientDetails.additionalAffiliation"] = { + $in: paramsObject.additionalAffiliations.map( + (additionalAffiliation) => new RegExp(additionalAffiliation, `i`), + ) }; } if (paramsObject.beiChapters) { diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index 43dffd3a..a539dda7 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -326,6 +326,10 @@ export const AdvancedSearch = (props: UpdateParamProp) => { loadChapters(); }, [loadChapters]); + useEffect(() => { + console.log("Redux additionalAffiliations:", additionalAffiliations); + }, [additionalAffiliations]); + return (
From 6c4331ed5ec1961a0647e631138b51ae4d520193 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 31 Jan 2025 20:31:01 -0500 Subject: [PATCH 24/26] filter by secondary phone num fixed, made IPatientSearchReducer's attributes consistent with PatientSearch Params --- common_utils/types.ts | 2 +- .../patient/analytics/group/page.tsx | 10 +++-- .../patient/search/page.tsx | 8 ++-- .../GroupSelector/GroupSelector.tsx | 4 +- .../Search/AdvancedSearch/AdvancedSearch.tsx | 40 +++++++++---------- .../reducers/patientSearchReducer/index.ts | 6 +-- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/common_utils/types.ts b/common_utils/types.ts index a1a2e582..c6181d5f 100644 --- a/common_utils/types.ts +++ b/common_utils/types.ts @@ -273,7 +273,7 @@ export type IPatientSearchReducer = { additionalAffiliations: Array; dateOfJoins: Array; beiChapters: Array; - secondaryPhoneNumbers: Array; + secondaryPhones: Array; secondaryNames: Array; }; diff --git a/src/app/(management-portal)/patient/analytics/group/page.tsx b/src/app/(management-portal)/patient/analytics/group/page.tsx index eab4780d..61f946ca 100644 --- a/src/app/(management-portal)/patient/analytics/group/page.tsx +++ b/src/app/(management-portal)/patient/analytics/group/page.tsx @@ -55,7 +55,7 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, } = useSelector( (patientSearchState: RootState) => patientSearchState.patientSearch, @@ -67,7 +67,7 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { emails, additionalAffiliations, secondaryNames, - secondaryPhones: secondaryPhoneNumbers, + secondaryPhones, beiChapters, active, countries, @@ -86,7 +86,7 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { emails, fullName, secondaryNames, - secondaryPhoneNumbers, + secondaryPhones, states, ], ); @@ -224,6 +224,10 @@ export default function Page({ params }: { params: { groupIds: string[] } }) { updateAllAnalytics(DateRangeEnum.RECENT); }, [params.groupIds, updateAllAnalytics]); + useEffect(() => { + console.log("Filters object:", filters); + }, [filters]); + return (
Patient Group Analytics | Brain Exercise Initiative diff --git a/src/app/(management-portal)/patient/search/page.tsx b/src/app/(management-portal)/patient/search/page.tsx index cc772337..6c2279e4 100644 --- a/src/app/(management-portal)/patient/search/page.tsx +++ b/src/app/(management-portal)/patient/search/page.tsx @@ -36,7 +36,7 @@ export default function Page() { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, } = useSelector((state: RootState) => state.patientSearch); @@ -61,7 +61,7 @@ export default function Page() { emails, additionalAffiliations, secondaryNames, - secondaryPhoneNumbers, + secondaryPhones, beiChapters, active, countries, @@ -90,7 +90,7 @@ export default function Page() { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, sortField, currentPage, @@ -110,7 +110,7 @@ export default function Page() { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, sortField, entriesPerPage, diff --git a/src/components/GroupSelector/GroupSelector.tsx b/src/components/GroupSelector/GroupSelector.tsx index d02ad695..1b3dcc93 100644 --- a/src/components/GroupSelector/GroupSelector.tsx +++ b/src/components/GroupSelector/GroupSelector.tsx @@ -43,7 +43,7 @@ function GroupSelector({ shownValue }: { shownValue: string }) { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, } = ss; @@ -80,7 +80,7 @@ function GroupSelector({ shownValue }: { shownValue: string }) { ...additionalAffiliations.map((v) => `Additional Affiliation: ${v}`), ...dateOfJoins.map((v) => `Date Joined: ${v}`), ...beiChapters.map((v) => `BEI Chapter: ${v}`), - ...secondaryPhoneNumbers.map((v) => `Secondary Phone: ${v}`), + ...secondaryPhones.map((v) => `Secondary Phone: ${v}`), ...secondaryNames.map((v) => `Secondary Name: ${v}`), ]; diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index a539dda7..fe8e9ddb 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -107,7 +107,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { const [additionalAffiliation, setAdditionalAffiliation] = useState(""); const [dateOfJoin, setDateOfJoin] = useState(""); const [beiChapter, setBeiChapter] = useState(""); - const [secondaryPhoneNumber, setSecondaryPhoneNumber] = useState(""); + const [secondaryPhone, setSecondaryPhone] = useState(""); const [secondaryName, setSecondaryName] = useState(""); const { @@ -120,7 +120,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, } = useSelector( (patientSearchState: RootState) => patientSearchState.patientSearch, @@ -137,7 +137,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { additionalAffiliations.length > 0 || dateOfJoins.length > 0 || beiChapters.length > 0 || - secondaryPhoneNumbers.length > 0 || + secondaryPhones.length > 0 || secondaryNames.length > 0, [ active, @@ -149,7 +149,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { additionalAffiliations, dateOfJoins, beiChapters, - secondaryPhoneNumbers, + secondaryPhones, secondaryNames, ], ); @@ -163,7 +163,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { additionalAffiliation !== "" || dateOfJoin !== "" || beiChapter !== "" || - secondaryPhoneNumber !== "" || + secondaryPhone !== "" || secondaryName !== "", [ country, @@ -174,7 +174,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { additionalAffiliation, dateOfJoin, beiChapter, - secondaryPhoneNumber, + secondaryPhone, secondaryName, ], ); @@ -203,7 +203,7 @@ export const AdvancedSearch = (props: UpdateParamProp) => { setDateOfJoin(""); setBeiChapter(""); setSecondaryName(""); - setSecondaryPhoneNumber(""); + setSecondaryPhone(""); setBeiChapter(""); }; @@ -222,9 +222,9 @@ export const AdvancedSearch = (props: UpdateParamProp) => { { condition: dateOfJoin, field: "dateOfJoins", value: dateOfJoins }, { condition: beiChapter, field: "beiChapters", value: beiChapters }, { - condition: secondaryPhoneNumber, - field: "secondaryPhoneNumbers", - value: secondaryPhoneNumbers, + condition: secondaryPhone, + field: "secondaryPhones", + value: secondaryPhones, }, { condition: secondaryName, @@ -327,8 +327,8 @@ export const AdvancedSearch = (props: UpdateParamProp) => { }, [loadChapters]); useEffect(() => { - console.log("Redux additionalAffiliations:", additionalAffiliations); - }, [additionalAffiliations]); + console.log("Redux secondaryPhones:", secondaryPhones); + }, [secondaryPhones]); return (
@@ -496,8 +496,8 @@ export const AdvancedSearch = (props: UpdateParamProp) => { )) : null} {tagsPresent - ? secondaryPhoneNumbers.length > 0 && - secondaryPhoneNumbers.map((currSecondaryPhoneNumber) => ( + ? secondaryPhones.length > 0 && + secondaryPhones.map((currSecondaryPhoneNumber) => (
{ title="Secondary Phone Number" value={currSecondaryPhoneNumber} handleClose={curryOnCloseSetTag( - secondaryPhoneNumbers, - "secondaryPhoneNumbers", + secondaryPhones, + "secondaryPhones", )} transformData={transformPhoneNumber} /> @@ -661,9 +661,9 @@ export const AdvancedSearch = (props: UpdateParamProp) => { setAdditionalAffiliation(e.target.value)} - value={additionalAffiliation} />
{
{ required={false} type="tel" placeholder="Enter Phone Number" - value={secondaryPhoneNumber} - onChange={(e) => setSecondaryPhoneNumber(e.target.value)} + value={secondaryPhone} + onChange={(e) => setSecondaryPhone(e.target.value)} />
diff --git a/src/redux/reducers/patientSearchReducer/index.ts b/src/redux/reducers/patientSearchReducer/index.ts index a20153ad..35d5d272 100644 --- a/src/redux/reducers/patientSearchReducer/index.ts +++ b/src/redux/reducers/patientSearchReducer/index.ts @@ -14,7 +14,7 @@ const initialState: IPatientSearchReducer = { additionalAffiliations: new Array(), dateOfJoins: new Array(), beiChapters: new Array(), - secondaryPhoneNumbers: new Array(), + secondaryPhones: new Array(), secondaryNames: new Array(), }; @@ -34,8 +34,8 @@ const setState = ( newState.additionalAffiliations ?? state.additionalAffiliations; state.dateOfJoins = newState.dateOfJoins ?? state.dateOfJoins; state.beiChapters = newState.beiChapters ?? state.beiChapters; - state.secondaryPhoneNumbers = - newState.secondaryPhoneNumbers ?? state.secondaryPhoneNumbers; + state.secondaryPhones = + newState.secondaryPhones ?? state.secondaryPhones; state.secondaryNames = newState.secondaryNames ?? state.secondaryNames; return state; From e68cebd6f65aa99878a0f2098d3d9da9ae36666a Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 31 Jan 2025 22:01:32 -0500 Subject: [PATCH 25/26] email filter case insensitive and has partial matching --- server/mongodb/actions/User.ts | 4 +++- src/components/Search/AdvancedSearch/AdvancedSearch.tsx | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index d2bb55d3..cd89df65 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -192,7 +192,9 @@ export const getUsersFiltered = async ({ } as UParam; if (paramsObject.emails) { - userParamsObject.email = { $in: paramsObject.emails }; + userParamsObject.email = { $in: paramsObject.emails.map( + (email) => new RegExp(email, `i`), + ) }; } if (paramsObject.secondaryNames) { userParamsObject["patientDetails.secondaryContactName"] = { diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index fe8e9ddb..a87dbb17 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -326,10 +326,6 @@ export const AdvancedSearch = (props: UpdateParamProp) => { loadChapters(); }, [loadChapters]); - useEffect(() => { - console.log("Redux secondaryPhones:", secondaryPhones); - }, [secondaryPhones]); - return (
From b9bb9eddd41744ad4d7ea0f1ea0a8cbc9809d0c6 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 31 Jan 2025 22:03:50 -0500 Subject: [PATCH 26/26] lint and formatting --- server/mongodb/actions/User.ts | 8 ++++---- src/components/Search/AdvancedSearch/AdvancedSearch.tsx | 6 ++++-- src/redux/reducers/patientSearchReducer/index.ts | 3 +-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/mongodb/actions/User.ts b/server/mongodb/actions/User.ts index cd89df65..4c40902d 100644 --- a/server/mongodb/actions/User.ts +++ b/server/mongodb/actions/User.ts @@ -192,9 +192,9 @@ export const getUsersFiltered = async ({ } as UParam; if (paramsObject.emails) { - userParamsObject.email = { $in: paramsObject.emails.map( - (email) => new RegExp(email, `i`), - ) }; + userParamsObject.email = { + $in: paramsObject.emails.map((email) => new RegExp(email, `i`)), + }; } if (paramsObject.secondaryNames) { userParamsObject["patientDetails.secondaryContactName"] = { @@ -221,7 +221,7 @@ export const getUsersFiltered = async ({ userParamsObject["patientDetails.additionalAffiliation"] = { $in: paramsObject.additionalAffiliations.map( (additionalAffiliation) => new RegExp(additionalAffiliation, `i`), - ) + ), }; } if (paramsObject.beiChapters) { diff --git a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx index a87dbb17..5c6f7821 100644 --- a/src/components/Search/AdvancedSearch/AdvancedSearch.tsx +++ b/src/components/Search/AdvancedSearch/AdvancedSearch.tsx @@ -562,7 +562,8 @@ export const AdvancedSearch = (props: UpdateParamProp) => { ) => { @@ -584,7 +585,8 @@ export const AdvancedSearch = (props: UpdateParamProp) => { ) => { diff --git a/src/redux/reducers/patientSearchReducer/index.ts b/src/redux/reducers/patientSearchReducer/index.ts index 35d5d272..d7e36407 100644 --- a/src/redux/reducers/patientSearchReducer/index.ts +++ b/src/redux/reducers/patientSearchReducer/index.ts @@ -34,8 +34,7 @@ const setState = ( newState.additionalAffiliations ?? state.additionalAffiliations; state.dateOfJoins = newState.dateOfJoins ?? state.dateOfJoins; state.beiChapters = newState.beiChapters ?? state.beiChapters; - state.secondaryPhones = - newState.secondaryPhones ?? state.secondaryPhones; + state.secondaryPhones = newState.secondaryPhones ?? state.secondaryPhones; state.secondaryNames = newState.secondaryNames ?? state.secondaryNames; return state;