Skip to content

Commit

Permalink
[Closes #116] Create a patients details page (#149)
Browse files Browse the repository at this point in the history
* Create skeleton of each section for patient page

* Style patient info section to match Figma more closely

* Style section titles

* Style contact information section

* Display medical information for a patient

* Include an empty array state for each medical section

* Style Contact info to be two columns with aligned rows

* Add missing key prop

* Consolidate some CSS rules

* Remove console logs

* Format file

* Remove unused variable

* Refactor PatientDetails into smaller components

* Add JSDoc comment and prop types

* Clean up imports

* Allow Pill text highlighting

* Display dashes if any information is blank

* Utilize consistent syntax for fallback text rendering

* Redirect register patient form after successful submission to patient details page

* Display QR code to link to patient details page

* Increase size of QR code to become scanable

* Display a bigger QR code

---------

Co-authored-by: Francis Li <[email protected]>
  • Loading branch information
samau3 and francisli authored Nov 7, 2024
1 parent 6e40cb9 commit 14b8ad5
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 18 deletions.
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>SF Life line</title>
<title>SF Life Line</title>
</head>
<body>
<div id="root"></div>
Expand Down
60 changes: 45 additions & 15 deletions client/src/pages/patients/patient-details/PatientDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useParams, useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import LifelineAPI from '../LifelineAPI.js';
import { StatusCodes } from 'http-status-codes';
import { useEffect } from 'react';
import { Loader } from '@mantine/core';
import { humanize } from 'inflection';
import { QRCode } from 'react-qrcode-logo';
import { Container, Grid, Loader, Text, Title } from '@mantine/core';

import LifelineAPI from '../LifelineAPI.js';
import ContactInfo from './components/ContactInfo.jsx';
import MedicalInfo from './components/MedicalInfo.jsx';
import Preferences from './components/Preferences.jsx';

import classes from './PatientDetails.module.css';

/**
*
Expand All @@ -12,8 +20,9 @@ import { Loader } from '@mantine/core';
export default function PatientDetails() {
const { patientId } = useParams();
const navigate = useNavigate();
const location = useLocation();

const { data, isSuccess, isError, isLoading } = useQuery({
const { data, isError, isLoading } = useQuery({
queryKey: ['patient'],
queryFn: async () => {
const res = await LifelineAPI.getPatient(patientId);
Expand All @@ -23,11 +32,7 @@ export default function PatientDetails() {
throw new Error('Failed to fetch patient.');
}
},

retry: false,
refetchOnWindowFocus: false,
});
console.log(data, isSuccess, isError, isLoading);

useEffect(() => {
if (isError) {
Expand All @@ -43,12 +48,37 @@ export default function PatientDetails() {
}

return (
<main>
<h1>Patient</h1>
<p>This is the patient page</p>
<p>Patient ID: {data?.id}</p>
<p>Patient First Name: {data?.firstName}</p>
<p>Patient Last Name: {data?.lastName}</p>
<main className={classes.details}>
<Container style={{ marginBottom: '2rem' }}>
<Grid my="2rem">
<Grid.Col span={{ base: 12, md: 8 }}>
<Title mb="1rem">
{data?.firstName} {data?.lastName}
</Title>
<section className={classes.patientInfoContainer}>
<Text>Date of birth</Text>
<Text>Gender</Text>
<Text>Preferred language</Text>
<Text>{data?.dateOfBirth}</Text>
<Text>{humanize(data?.gender)}</Text>
<Text>{humanize(data?.language)}</Text>
</section>
</Grid.Col>
<Grid.Col display={{ base: 'none', md: 'block' }} span={4} ta="right">
<QRCode value={`${window.location.origin}${location.pathname}`} />
</Grid.Col>
</Grid>
<ContactInfo
emergencyContact={data?.emergencyContact}
physician={data?.physician}
/>
<MedicalInfo
allergies={data?.allergies}
medications={data?.medications}
conditions={data?.conditions}
/>
<Preferences codeStatus={data?.codeStatus} hospital={data?.hospital} />
</Container>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.boldText {
font-weight: 600;
margin-top: var(--mantine-spacing-sm);
margin-bottom: var(--mantine-spacing-sm);
}

.patientInfoContainer {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto;
text-align: center;
gap: var(--mantine-spacing-sm);
}

.patientInfoContainer p:nth-child(-n + 3) {
font-weight: 600;
margin-bottom: 0;
}

.patientInfoContainer p:nth-child(n + 4) {
margin-top: 0;
}

.sectionTitle {
font-size: var(--mantine-font-size-xl);
font-weight: 600;
margin-top: var(--mantine-spacing-md);
margin-bottom: var(--mantine-spacing-md);
}

.titleRow {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--mantine-spacing-xl);
margin-bottom: var(--mantine-spacing-md);
}

.twoColumnGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--mantine-spacing-xl);
}

.contactRow {
gap: var(--mantine-spacing-md);
margin-bottom: var(--mantine-spacing-xs);
}

.contactInfoColumnTitle {
font-weight: 600;
font-size: var(--mantine-font-size-lg);
}

.medicalInfoPills {
margin-right: var(--mantine-spacing-sm);
margin-bottom: var(--mantine-spacing-sm);
user-select: text;
}

@media (min-width: 768px) {
.patientInfoContainer {
text-align: left;
width: 70%;
}

.details {
max-height: 100vh;
max-height: 100dvh;
overflow-y: auto;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from 'prop-types';

import { Paper, Text } from '@mantine/core';
import { humanize } from 'inflection';
import classes from '../PatientDetails.module.css';

const contactInfoProps = {
emergencyContact: PropTypes.object,
physician: PropTypes.object,
};

ContactInfo.propTypes = contactInfoProps;

/**
*
* @param {PropTypes.InferProps<typeof contactInfoProps>} props
*/
export default function ContactInfo({ emergencyContact, physician }) {
return (
<section>
<Text className={classes.sectionTitle}> Contact Information</Text>
<Paper shadow="xs" p="md" radius="md" withBorder>
<div className={classes.titleRow}>
<Text className={classes.contactInfoColumnTitle}>
Emergency Contact
</Text>
<Text className={classes.contactInfoColumnTitle}>
Primary care physician (PCP) contact
</Text>
</div>
<div className={classes.twoColumnGrid}>
<section>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Name</Text>
<Text>
{emergencyContact
? `${emergencyContact?.firstName} ${emergencyContact?.lastName}`
: '-'}
</Text>
</div>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Phone</Text>
<Text>
{emergencyContact?.phone ? emergencyContact?.phone : '-'}
</Text>
</div>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Relationship</Text>
<Text>
{emergencyContact?.relationship
? humanize(emergencyContact?.relationship)
: '-'}
</Text>
</div>
</section>
<section>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Name</Text>
<Text>
{physician
? `${physician?.firstName} ${physician?.lastName}`
: '-'}
</Text>
</div>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Phone</Text>
<Text>{physician?.phone ? physician?.phone : '-'}</Text>
</div>
<div className={classes.contactRow}>
<Text className={classes.boldText}>Hospital</Text>
<Text>
{physician?.hospitals[0]?.name
? physician?.hospitals[0]?.name
: '-'}
</Text>
</div>
</section>
</div>
</Paper>
</section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import PropTypes from 'prop-types';

import { Paper, Text, Pill } from '@mantine/core';
import classes from '../PatientDetails.module.css';

const medicalInfoProps = {
allergies: PropTypes.array,
medications: PropTypes.array,
conditions: PropTypes.array,
};

MedicalInfo.propTypes = medicalInfoProps;

/**
*
* @param {PropTypes.InferProps<typeof medicalInfoProps>} props
*/
export default function MedicalInfo({ allergies, medications, conditions }) {
return (
<section>
<Text className={classes.sectionTitle}>Medical Information</Text>
<Paper shadow="xs" p="md" radius="md" withBorder>
<section>
<Text className={classes.boldText}>Allergies</Text>
{allergies.length === 0 ? (
<Text>None</Text>
) : (
allergies.map((entry) => (
<Pill
size="md"
key={entry.allergy.id}
className={classes.medicalInfoPills}
>
{entry.allergy.name}
</Pill>
))
)}
</section>
<section>
<Text className={classes.boldText}>Medications</Text>
{medications.length === 0 ? (
<Text>None</Text>
) : (
medications.map((entry) => (
<Pill
size="md"
key={entry.medication.id}
className={classes.medicalInfoPills}
>
{entry.medication.name}
</Pill>
))
)}
</section>
<section>
<Text className={classes.boldText}>Conditions</Text>
{conditions?.length === 0 ? (
<Text>None</Text>
) : (
<ul>
{conditions.map((entry) => (
<li key={entry.condition.id}>{entry.condition.name}</li>
))}
</ul>
)}
</section>
</Paper>
</section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import { Paper, Text } from '@mantine/core';
import classes from '../PatientDetails.module.css';
import { humanize } from 'inflection';
const preferencesProps = {
codeStatus: PropTypes.string,
hospital: PropTypes.object,
};

Preferences.propTypes = preferencesProps;

/**
* Preferences section of patient details
* @param {PropTypes.InferProps<typeof preferencesProps>} props
*/
export default function Preferences({ codeStatus, hospital }) {
return (
<section>
<Text className={classes.sectionTitle}>Preferences</Text>
<Paper shadow="xs" p="md" radius="md" withBorder>
<section>
<Text className={classes.boldText}>Code status</Text>
<Text>{codeStatus ? humanize(codeStatus) : 'Not provided'}</Text>
<Text className={classes.boldText}>Hospital</Text>
<Text>{hospital ? hospital.name : 'Not provided'}</Text>
</section>
</Paper>
</section>
);
}
4 changes: 2 additions & 2 deletions client/src/pages/patients/register/PatientRegistration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export default function PatientRegistration() {
);
if (updateRes.status === StatusCodes.OK) {
showSuccessNotification('Successfully registered patient.');
navigate('/dashboard', { replace: true });
navigate(`/patients/${patientId}`, { replace: true });
return;
}
}
Expand All @@ -299,7 +299,7 @@ export default function PatientRegistration() {
showSuccessNotification(
'Patient basic information has been successfully updated.',
);
navigate('/dashboard', { replace: true });
navigate(`/patients/${patientId}`, { replace: true });
return;
}
}
Expand Down

0 comments on commit 14b8ad5

Please sign in to comment.