From 6cdb971207eeab6bcfd45fcd40be6397ffe43269 Mon Sep 17 00:00:00 2001 From: kl5331 Date: Thu, 1 Feb 2024 13:24:28 -0500 Subject: [PATCH 1/4] Added code and leveraging dummy data. --- apps/rmh-web/app/globals.css | 8 +-- apps/rmh-web/app/pt-portal/dummyData.tsx | 22 ++++++++ apps/rmh-web/app/pt-portal/getUsers.tsx | 45 ++++++++++++++++ apps/rmh-web/app/pt-portal/models.ts | 8 ++- apps/rmh-web/app/pt-portal/page.tsx | 14 ++--- apps/rmh-web/components/SideBar.tsx | 68 ++++++++++++++++++++++++ apps/rmh-web/components/Tab.tsx | 28 ++++++++++ apps/rmh-web/components/TopBar.tsx | 29 +++++----- apps/rmh-web/components/UsersList.tsx | 44 +++++++++++++++ apps/rmh-web/next.config.js | 9 +++- apps/rmh-web/package-lock.json | 9 ++++ apps/rmh-web/package.json | 1 + apps/rmh-web/public/chat-icon.svg | 3 ++ apps/rmh-web/public/profile-icon.svg | 6 +++ apps/rmh-web/public/search-icon.svg | 3 ++ 15 files changed, 265 insertions(+), 32 deletions(-) create mode 100644 apps/rmh-web/app/pt-portal/dummyData.tsx create mode 100644 apps/rmh-web/app/pt-portal/getUsers.tsx create mode 100644 apps/rmh-web/components/SideBar.tsx create mode 100644 apps/rmh-web/components/Tab.tsx create mode 100644 apps/rmh-web/components/UsersList.tsx create mode 100644 apps/rmh-web/public/chat-icon.svg create mode 100644 apps/rmh-web/public/profile-icon.svg create mode 100644 apps/rmh-web/public/search-icon.svg diff --git a/apps/rmh-web/app/globals.css b/apps/rmh-web/app/globals.css index 86005d0..f529a81 100644 --- a/apps/rmh-web/app/globals.css +++ b/apps/rmh-web/app/globals.css @@ -17,13 +17,7 @@ } body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); + background-color: lightgray; } .column { diff --git a/apps/rmh-web/app/pt-portal/dummyData.tsx b/apps/rmh-web/app/pt-portal/dummyData.tsx new file mode 100644 index 0000000..a258e6d --- /dev/null +++ b/apps/rmh-web/app/pt-portal/dummyData.tsx @@ -0,0 +1,22 @@ +export const dummyAppointmentData = [{ + ptId: "6b87d552-a2fe-465a-998c-1b288fee212f", + appointmentDate: "2024-02-01T10:00:00", + createdOn: "2024-01-31T15:30:00", + duration: 30, + patientName: "John Doe", + zoomUrl: "https://example.com/zoom-meeting" +}, { + ptId: "6b87d552-a2fe-465a-998c-1b288fee212f", + appointmentDate: "2024-01-01T10:00:00", + createdOn: "2024-01-31T15:30:00", + duration: 30, + patientName: "Jane Doe", + zoomUrl: "https://example.com/zoom-meeting" +}, { + ptId: "6b87d552-a2fe-465a-998c-1b288fee212f", + appointmentDate: "2024-02-10T10:00:00", + createdOn: "2024-01-31T15:30:00", + duration: 30, + patientName: "Doe Doe", + zoomUrl: "https://example.com/zoom-meeting" +}]; diff --git a/apps/rmh-web/app/pt-portal/getUsers.tsx b/apps/rmh-web/app/pt-portal/getUsers.tsx new file mode 100644 index 0000000..e729c8f --- /dev/null +++ b/apps/rmh-web/app/pt-portal/getUsers.tsx @@ -0,0 +1,45 @@ +'use server' + +import '@aws-amplify/ui-react/styles.css'; +import '../globals.css'; +import {User} from "@/app/pt-portal/models"; +import axios from "axios"; +import gql from 'graphql-tag'; +import {dummyAppointmentData} from "@/app/pt-portal/dummyData"; + +export async function getUsers(ptID: string) { + console.log(ptID) + const GRAPHQL_API_URL_API_KEY = process.env.GRAPHQL_API_URL_API_KEY ? process.env.GRAPHQL_API_URL_API_KEY : ''; + const GRAPHQL_API_URL = process.env.GRAPHQL_API_URL ? process.env.GRAPHQL_API_URL : ''; + const query = gql` + query MyQuery { + getAppointments(ptId: "${ptID}") { + items { + appointmentDate + createdOn + duration + patientName + ptId + zoomUrl + } + } + } +` + try { + // const AuthStr = 'Bearer '.concat(GRAPHQL_API_URL_API_KEY); + // const { data }: { data: User[] } = await axios.post( + // `https://acnxb73revg5rbelc22nrft7re.appsync-api.us-east-1.amazonaws.com/graphql`, { + // headers: {Authorization: AuthStr}, query + // }); + + return dummyAppointmentData.sort( + (a, b) => + new Date(a.appointmentDate).getTime() - + new Date(b.appointmentDate).getTime() + ) + } catch (err) { + console.log(err); + return [] + + } +} \ No newline at end of file diff --git a/apps/rmh-web/app/pt-portal/models.ts b/apps/rmh-web/app/pt-portal/models.ts index c277fec..0d6b945 100644 --- a/apps/rmh-web/app/pt-portal/models.ts +++ b/apps/rmh-web/app/pt-portal/models.ts @@ -4,6 +4,10 @@ export interface Client { } export interface User { - id: string; - name: string; + ptId: string; + appointmentDate: string; + createdOn: string; + duration: number; + patientName: string; + zoomUrl: string; } diff --git a/apps/rmh-web/app/pt-portal/page.tsx b/apps/rmh-web/app/pt-portal/page.tsx index 0c7211f..92e569d 100644 --- a/apps/rmh-web/app/pt-portal/page.tsx +++ b/apps/rmh-web/app/pt-portal/page.tsx @@ -1,13 +1,14 @@ -'use client' - import '@aws-amplify/ui-react/styles.css'; import '../globals.css'; import Head from 'next/head'; import TopBar from '@/components/TopBar'; +import SideBar from "@/components/SideBar"; +import {User} from "@/app/pt-portal/models"; +import {getUsers} from "@/app/pt-portal/getUsers"; - -export default function PTPortal() { - +export default async function PTPortal() { + const ptID: string = '6b87d552-a2fe-465a-998c-1b288fee212f'; + const users: User[] = await getUsers(ptID); return (
@@ -15,7 +16,8 @@ export default function PTPortal() { -
+
+
diff --git a/apps/rmh-web/components/SideBar.tsx b/apps/rmh-web/components/SideBar.tsx new file mode 100644 index 0000000..138c2c1 --- /dev/null +++ b/apps/rmh-web/components/SideBar.tsx @@ -0,0 +1,68 @@ +"use client"; +import { User } from "@/app/pt-portal/models"; +import Image from "next/image"; +import { useMemo, useState } from "react"; + +import UsersList from "./UsersList"; +import Tab from "./Tab"; + +const tabs = ["Upcoming Appointments", "Past Appointments"]; + +const useFinalUsers = (activeIndex: number, search: string, users: User[]) => + useMemo(() => { + const userFilter = (user: User) => + new Date(user.appointmentDate).getTime() > new Date().getTime(); + const filteredUsers = activeIndex === 0 ? users.filter(userFilter) : users; + + return search.trim().length > 0 + ? filteredUsers.filter((user) => + user.patientName.toLowerCase().startsWith(search) + ) + : filteredUsers; + }, [activeIndex, search, users]); + +const SideBar = ({ + users, +}: { + users: User[]; +}) => { + const [search, setSearch] = useState(""); + const [activeIndex, setActiveIndex] = useState(0); + const finalUsers = useFinalUsers(activeIndex, search, users); + + return ( +
+
+ My Appointments +
+
+ {tabs.map((tab, index) => ( + setActiveIndex(index)} + text={tab} + /> + ))} +
+
+ search icon + setSearch(e.target.value)} + /> +
+ +
+ ); +}; + +export default SideBar; diff --git a/apps/rmh-web/components/Tab.tsx b/apps/rmh-web/components/Tab.tsx new file mode 100644 index 0000000..f74513e --- /dev/null +++ b/apps/rmh-web/components/Tab.tsx @@ -0,0 +1,28 @@ +import clsx from "clsx"; + +const Tab = ({ + isActive, + text, + setActive, +}: { + isActive: boolean; + text: string; + setActive: () => void; +}) => { + const color = isActive + ? "border-[#2B478B] text-[#2B478B]" + : "border-[#C3C6CC] text-[343741] transition-opacity hover:opacity-50"; + + const className = clsx( + "cursor-pointer border-b-[2px] font-bold text-[16px] pb-2", + color + ); + + return ( +
+ {text} +
+ ); +}; + +export default Tab; diff --git a/apps/rmh-web/components/TopBar.tsx b/apps/rmh-web/components/TopBar.tsx index ed9aa41..1a22933 100644 --- a/apps/rmh-web/components/TopBar.tsx +++ b/apps/rmh-web/components/TopBar.tsx @@ -1,18 +1,18 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { selectIsAuthenticated } from '@/features/authSlice'; -import { Auth } from 'aws-amplify'; +// import { useSelector } from 'react-redux'; +// import { selectIsAuthenticated } from '@/features/authSlice'; +// import { Auth } from 'aws-amplify'; const TopBar = () => { - const isAuthenticated = useSelector(selectIsAuthenticated); - - const handleSignOut = async () => { - try { - await Auth.signOut(); - } catch (error) { - console.error('Error signing out:', error); - } - }; + // const isAuthenticated = useSelector(selectIsAuthenticated); + // + // const handleSignOut = async () => { + // try { + // await Auth.signOut(); + // } catch (error) { + // console.error('Error signing out:', error); + // } + // }; return (
@@ -20,10 +20,9 @@ const TopBar = () => { Logo

Right Move Health

- {isAuthenticated ? ( + {
- ) : null} + }
); }; diff --git a/apps/rmh-web/components/UsersList.tsx b/apps/rmh-web/components/UsersList.tsx new file mode 100644 index 0000000..4de51ff --- /dev/null +++ b/apps/rmh-web/components/UsersList.tsx @@ -0,0 +1,44 @@ +import { User } from "@/app/pt-portal/models"; +import Image from "next/image"; +// import { isSelected, setUsers } from "@/features/usersSlice"; +// import { useDispatch } from "react-redux"; +// import { useSelector } from "react-redux"; +import clsx from "clsx"; + +const UserItem = ({ user }: { user: User }) => { + // const dispatch = useDispatch(); + // const userSelected = useSelector(isSelected)(user); + const selectedStyles = clsx( + `flex items-center justify-between p-4 hover:bg-[#EFF3FC] transition-all duration-250` + ); +//onClick={() => dispatch(setUsers(user))} + return ( +
+
+ profile icon +
{user.patientName}
+
+ +
+ ); +}; + +const UsersList = ({ users }: { users: User[] }) => ( + <> + {users.map((user) => ( + + ))} + +); + +export default UsersList; diff --git a/apps/rmh-web/next.config.js b/apps/rmh-web/next.config.js index 767719f..5feaa51 100644 --- a/apps/rmh-web/next.config.js +++ b/apps/rmh-web/next.config.js @@ -1,4 +1,9 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} -module.exports = nextConfig +module.exports = { + experimental: { + serverActions: { + allowedOrigins: ['my-proxy.com', '*.my-proxy.com'], + }, + }, +} \ No newline at end of file diff --git a/apps/rmh-web/package-lock.json b/apps/rmh-web/package-lock.json index 36c639a..78c463f 100644 --- a/apps/rmh-web/package-lock.json +++ b/apps/rmh-web/package-lock.json @@ -17,6 +17,7 @@ "autoprefixer": "10.4.15", "aws-amplify": "^5.3.10", "axios": "^1.4.0", + "clsx": "^2.1.0", "eslint": "8.47.0", "eslint-config-next": "13.4.19", "graphql": "^16.8.0", @@ -10598,6 +10599,14 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/apps/rmh-web/package.json b/apps/rmh-web/package.json index 2d7c35d..ffaeca0 100644 --- a/apps/rmh-web/package.json +++ b/apps/rmh-web/package.json @@ -18,6 +18,7 @@ "autoprefixer": "10.4.15", "aws-amplify": "^5.3.10", "axios": "^1.4.0", + "clsx": "^2.1.0", "eslint": "8.47.0", "eslint-config-next": "13.4.19", "graphql": "^16.8.0", diff --git a/apps/rmh-web/public/chat-icon.svg b/apps/rmh-web/public/chat-icon.svg new file mode 100644 index 0000000..0cdd05d --- /dev/null +++ b/apps/rmh-web/public/chat-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/rmh-web/public/profile-icon.svg b/apps/rmh-web/public/profile-icon.svg new file mode 100644 index 0000000..7b9aaee --- /dev/null +++ b/apps/rmh-web/public/profile-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/rmh-web/public/search-icon.svg b/apps/rmh-web/public/search-icon.svg new file mode 100644 index 0000000..e766534 --- /dev/null +++ b/apps/rmh-web/public/search-icon.svg @@ -0,0 +1,3 @@ + + + From 3406e156ebfdde02ca19d42c1969580fd833ff13 Mon Sep 17 00:00:00 2001 From: kl5331 Date: Thu, 1 Feb 2024 13:26:16 -0500 Subject: [PATCH 2/4] Updated Tab naming convention --- .gitignore | 7 ++++++- apps/rmh-web/app/layout.tsx | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6708349..0d2fdc0 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,9 @@ apps/**/next-env.d.ts /sls/layers/**/python /sls/**/targetsls/**/*.jar -/sls/**/*.dev \ No newline at end of file +/sls/**/*.dev +/.idea/.gitignore +/.idea/kl-rmh-coding-challenge.iml +/.idea/modules.xml +/.idea/inspectionProfiles/Project_Default.xml +/.idea/vcs.xml diff --git a/apps/rmh-web/app/layout.tsx b/apps/rmh-web/app/layout.tsx index ae84562..7e01a2f 100644 --- a/apps/rmh-web/app/layout.tsx +++ b/apps/rmh-web/app/layout.tsx @@ -5,8 +5,8 @@ import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: 'My Portal', + description: 'A portal to view patient data', } export default function RootLayout({ From 3eedd05be7d3ed7ecfe4857cc3953056f9ca83bc Mon Sep 17 00:00:00 2001 From: kl5331 Date: Thu, 1 Feb 2024 13:36:58 -0500 Subject: [PATCH 3/4] Fixed Filtering Issue --- apps/rmh-web/components/SideBar.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/rmh-web/components/SideBar.tsx b/apps/rmh-web/components/SideBar.tsx index 138c2c1..046e21b 100644 --- a/apps/rmh-web/components/SideBar.tsx +++ b/apps/rmh-web/components/SideBar.tsx @@ -2,24 +2,19 @@ import { User } from "@/app/pt-portal/models"; import Image from "next/image"; import { useMemo, useState } from "react"; - import UsersList from "./UsersList"; import Tab from "./Tab"; - const tabs = ["Upcoming Appointments", "Past Appointments"]; -const useFinalUsers = (activeIndex: number, search: string, users: User[]) => +const useFinalUsers = (search: string, users: User[]) => useMemo(() => { - const userFilter = (user: User) => - new Date(user.appointmentDate).getTime() > new Date().getTime(); - const filteredUsers = activeIndex === 0 ? users.filter(userFilter) : users; return search.trim().length > 0 - ? filteredUsers.filter((user) => + ? users.filter((user) => user.patientName.toLowerCase().startsWith(search) ) - : filteredUsers; - }, [activeIndex, search, users]); + : users; + }, [search, users]); const SideBar = ({ users, @@ -28,7 +23,7 @@ const SideBar = ({ }) => { const [search, setSearch] = useState(""); const [activeIndex, setActiveIndex] = useState(0); - const finalUsers = useFinalUsers(activeIndex, search, users); + const finalUsers = useFinalUsers(search, users); return (
@@ -60,7 +55,7 @@ const SideBar = ({ onChange={(e) => setSearch(e.target.value)} />
- + ); }; From 0ec31d8cce411d40bbcbad18859fd0140ad51542 Mon Sep 17 00:00:00 2001 From: kl5331 Date: Thu, 1 Feb 2024 14:25:16 -0500 Subject: [PATCH 4/4] Fixed graphql and filtering --- apps/rmh-web/app/pt-portal/getUsers.tsx | 29 ++++++++++++++++--------- apps/rmh-web/components/SideBar.tsx | 1 - 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/rmh-web/app/pt-portal/getUsers.tsx b/apps/rmh-web/app/pt-portal/getUsers.tsx index e729c8f..60263e4 100644 --- a/apps/rmh-web/app/pt-portal/getUsers.tsx +++ b/apps/rmh-web/app/pt-portal/getUsers.tsx @@ -5,10 +5,9 @@ import '../globals.css'; import {User} from "@/app/pt-portal/models"; import axios from "axios"; import gql from 'graphql-tag'; -import {dummyAppointmentData} from "@/app/pt-portal/dummyData"; +import { print } from 'graphql/language/printer' export async function getUsers(ptID: string) { - console.log(ptID) const GRAPHQL_API_URL_API_KEY = process.env.GRAPHQL_API_URL_API_KEY ? process.env.GRAPHQL_API_URL_API_KEY : ''; const GRAPHQL_API_URL = process.env.GRAPHQL_API_URL ? process.env.GRAPHQL_API_URL : ''; const query = gql` @@ -26,19 +25,29 @@ export async function getUsers(ptID: string) { } ` try { - // const AuthStr = 'Bearer '.concat(GRAPHQL_API_URL_API_KEY); - // const { data }: { data: User[] } = await axios.post( - // `https://acnxb73revg5rbelc22nrft7re.appsync-api.us-east-1.amazonaws.com/graphql`, { - // headers: {Authorization: AuthStr}, query - // }); - - return dummyAppointmentData.sort( + const data: User[] = await axios.post(GRAPHQL_API_URL, { query: print(query) }, + { + headers: { + 'Content-Type': 'application/json', + 'x-api-key': GRAPHQL_API_URL_API_KEY + } + }) + .then(response => { + const items: User[] = response.data.data.getAppointments.items; + console.log('Response:', items); + return items + }) + .catch(error => { + console.error('Error:', error.response ? error.response.data : error.message); + return [] + }); + return data.sort( (a, b) => new Date(a.appointmentDate).getTime() - new Date(b.appointmentDate).getTime() ) } catch (err) { - console.log(err); + console.log('Formatting Error'); return [] } diff --git a/apps/rmh-web/components/SideBar.tsx b/apps/rmh-web/components/SideBar.tsx index 046e21b..a001d1f 100644 --- a/apps/rmh-web/components/SideBar.tsx +++ b/apps/rmh-web/components/SideBar.tsx @@ -8,7 +8,6 @@ const tabs = ["Upcoming Appointments", "Past Appointments"]; const useFinalUsers = (search: string, users: User[]) => useMemo(() => { - return search.trim().length > 0 ? users.filter((user) => user.patientName.toLowerCase().startsWith(search)