Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kevin Lewis Coding Challenge #12

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ apps/**/next-env.d.ts
/sls/layers/**/python
/sls/**/targetsls/**/*.jar

/sls/**/*.dev
/sls/**/*.dev
/.idea/.gitignore
/.idea/kl-rmh-coding-challenge.iml
/.idea/modules.xml
/.idea/inspectionProfiles/Project_Default.xml
/.idea/vcs.xml
8 changes: 1 addition & 7 deletions apps/rmh-web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions apps/rmh-web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
22 changes: 22 additions & 0 deletions apps/rmh-web/app/pt-portal/dummyData.tsx
Original file line number Diff line number Diff line change
@@ -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"
}];
54 changes: 54 additions & 0 deletions apps/rmh-web/app/pt-portal/getUsers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'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 { print } from 'graphql/language/printer'

export async function getUsers(ptID: string) {
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 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('Formatting Error');
return []

}
}
8 changes: 6 additions & 2 deletions apps/rmh-web/app/pt-portal/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 8 additions & 6 deletions apps/rmh-web/app/pt-portal/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
'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 (
<section>
<Head>
<title>PT Portal</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<TopBar />
<main className='flex min-h-screen justify-center w-full'>
<main className="flex min-h-screen w-full">
<SideBar users={users} />
</main>

</section>
Expand Down
62 changes: 62 additions & 0 deletions apps/rmh-web/components/SideBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"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 = (search: string, users: User[]) =>
useMemo(() => {
return search.trim().length > 0
? users.filter((user) =>
user.patientName.toLowerCase().startsWith(search)
)
: users;
}, [search, users]);

const SideBar = ({
users,
}: {
users: User[];
}) => {
const [search, setSearch] = useState("");
const [activeIndex, setActiveIndex] = useState(0);
const finalUsers = useFinalUsers(search, users);

return (
<div className="h-full bg-white w-100 p-0.5 rounded-t-lg">
<div className="font-bold text-[#343741] text-[22px] p-6">
My Appointments
</div>
<div className="grid grid-cols-2 text-center">
{tabs.map((tab, index) => (
<Tab
key={tab}
isActive={activeIndex === index}
setActive={() => setActiveIndex(index)}
text={tab}
/>
))}
</div>
<div className="p-4 relative">
<Image
src="/search-icon.svg"
height={20}
width={20}
alt="search icon"
className="absolute top-1/2 left-[34px] -translate-y-1/2"
/>
<input
className="border-[1px] border-[#C3C6CC] w-full p-2 rounded-lg placeholder:text-[#6B6F7B] pl-14"
placeholder="Search Client"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<UsersList users={finalUsers} />
</div>
);
};

export default SideBar;
28 changes: 28 additions & 0 deletions apps/rmh-web/components/Tab.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div onClick={setActive} className={"cursor-pointer border-b-[2px] font-bold text-[16px] pb-2 hover: "}>
{text}
</div>
);
};

export default Tab;
29 changes: 14 additions & 15 deletions apps/rmh-web/components/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
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 (
<header className="bg-white p-4 flex justify-between items-center">
<div className="flex items-center">
<img src="/logo.png" alt="Logo" className="h-8 mr-4" />
<h1 className="text-white text-lg font-bold">Right Move Health</h1>
</div>
{isAuthenticated ? (
{
<div className="flex items-center">
<button
onClick={handleSignOut}
className="text-white hover:underline cursor-pointer mr-4"
>
Sign Out
Expand All @@ -34,7 +33,7 @@ const TopBar = () => {
className="h-10 w-10 rounded-full"
/>
</div>
) : null}
}
</header>
);
};
Expand Down
44 changes: 44 additions & 0 deletions apps/rmh-web/components/UsersList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={selectedStyles}>
<div className="flex items-center gap-4">
<Image
src="/profile-icon.svg"
width={40}
height={40}
alt="profile icon"
/>
<div>{user.patientName}</div>
</div>
<button
className="hover:opacity-50 duration-100"
onClick={() => console.log("opens chat window")}
>
<Image src="/chat-icon.svg" width={24} height={24} alt="chat icon" onClick={() => alert('Chat feature coming soon!')}/>
</button>
</div>
);
};

const UsersList = ({ users }: { users: User[] }) => (
<>
{users.map((user) => (
<UserItem key={JSON.stringify(user)} user={user} />
))}
</>
);

export default UsersList;
9 changes: 7 additions & 2 deletions apps/rmh-web/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}

module.exports = nextConfig
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}
9 changes: 9 additions & 0 deletions apps/rmh-web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/rmh-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions apps/rmh-web/public/chat-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading