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

Dynamic medical emergency page #16

Merged
merged 10 commits into from
Feb 26, 2024
4 changes: 2 additions & 2 deletions dfm-sideline-sidekick-app/ConditionSectionStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default StyleSheet.create({
alignItems: "flex-start",
justifyContent: "space-evenly",
width: "100%",
marginTop: 45,
marginTop: 16,
},
menuText: {
fontFamily: "Roboto-Regular",
Expand Down Expand Up @@ -73,7 +73,7 @@ export default StyleSheet.create({
},
information: {
marginLeft: 16,
marginTop: 20,
marginTop: 0,
marginRight: 16,
},
overview: {},
Expand Down
191 changes: 116 additions & 75 deletions dfm-sideline-sidekick-app/ConditionsSection.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall, make sure to get rid of any commented code. Also, note that the Condiitons Section will be showing data that is fetched from the device's storage, not from the MongoDB database. So for the scope of this pull request, just don't worry about fetching the data, and just make the data be passed in as a prop to the component. In the future, we will have a dedicated file that fetches the correct data for a specific emergency/condition, and passes it into the Conditions Section component

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the progress you have made on emergencies.ts and the fetching will be useful later, especially for admin mode stuff, so great work and feel free to keep this! Just make sure the ConditionsSection.tsx only focuses on showing data that is passed in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes as discussed here have been addressed with the commits below by having ConditionsSection take in an Emergency as input as opposed to an id. Tests have been reconfigured accordingly.

Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
// import { useRoute } from "@react-navigation/native";
import { ParamListBase, RouteProp } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
import * as Font from "expo-font";
import { useEffect, useState } from "react";
import { Image, Pressable, SafeAreaView, ScrollView, Text, View } from "react-native";

import styles from "./ConditionSectionStyles";
import StringRenderer from "./components/StringRenderer";
// import { getEmergency } from "./emergencies";

export default function ConditionsSection() {
import type { Emergency } from "./emergencies";

export type RootStackParamList = {
// Define the parameters for your screens here
Conditions: { emergency: Emergency }; // Example parameter
} & ParamListBase;

// Define the type for the route parameters
type ConditionsScreenRouteProp = RouteProp<RootStackParamList, "Conditions">;

// Define the type for the navigation object
type ConditionsScreenNavigationProp = StackNavigationProp<RootStackParamList, "Conditions">;

type Props = {
route: ConditionsScreenRouteProp;
navigation: ConditionsScreenNavigationProp;
};

type StringValue = string | string[] | { [key: string]: StringValue };

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function ConditionsSection({ route, navigation }: Props) {
const [isOverviewPressed, setIsOverviewPressed] = useState<boolean>(true);
const [isTreatmentPressed, setIsTreatmentPressed] = useState<boolean>(false);
const [isFontsLoaded, setIsFontsLoaded] = useState<boolean>(false);
const [overviewHeaders, setOverviewHeaders] = useState<string[]>([]);
const [overviewValues, setOverviewValues] = useState<StringValue[]>([]);
const [treatmentHeaders, setTreatmentHeaders] = useState<string[]>([]);
const [treatmentValues, setTreatmentValues] = useState<StringValue[]>([]);
const [contentHeaders, setContentHeaders] = useState<string[]>([]);
const [contentValues, setContentValues] = useState<StringValue[]>([]);

const { params } = route; // Destructure params from the route object
const [emergency, setEmergency] = useState<Emergency>();

useEffect(() => {
async function loadFont() {
Expand All @@ -27,20 +62,37 @@ export default function ConditionsSection() {
void loadFont();
}, []);

type BulletListProps = {
items: string[];
};
useEffect(() => {
// if (params?.emergencyObjectId) {
// // Check if params and emergencyObjectId exist
// // eslint-disable-next-line @typescript-eslint/no-floating-promises
// getEmergency(params.emergencyObjectId).then((result) => {
// if (result.success) {
// setEmergency(result.data);
// } else {
// console.error("Error fetching emergency data:", result.error);
// }
// });
// }
setEmergency(params.emergency);
}, [params]); // Include params in the dependency array

useEffect(() => {
if (emergency?.overview && typeof emergency.overview === "object") {
setOverviewHeaders(Object.keys(emergency.overview));
setOverviewValues(Object.values(emergency.overview) as StringValue[]);
}

const BulletList = ({ items }: BulletListProps) => (
<View style={styles.list}>
{items.map((item: string, index: number) => (
<View key={index} style={styles.listItem}>
<Text style={styles.bullet}>{"\u2022"}</Text>
<Text style={styles.itemText}>{item}</Text>
</View>
))}
</View>
);
if (emergency?.treatment && typeof emergency.treatment === "object") {
setTreatmentHeaders(Object.keys(emergency.treatment));
setTreatmentValues(Object.values(emergency.treatment) as StringValue[]);
}

if (emergency?.content && typeof emergency.content === "object") {
setContentHeaders(Object.keys(emergency.content));
setContentValues(Object.values(emergency.content) as StringValue[]);
}
}, [emergency]);

function onOverviewPress() {
if (!isOverviewPressed) {
Expand All @@ -67,7 +119,7 @@ export default function ConditionsSection() {
<Image style={styles.image} source={require("./assets/ic_caretleft.png")} />
<View style={styles.margin}>
<Text style={styles.subtitle}>Medical Emergency</Text>
<Text style={styles.title}>Cervical Spine Injury</Text>
{emergency && <Text style={styles.title}>{emergency.title}</Text>}
</View>

<View style={styles.menu}>
Expand All @@ -91,69 +143,58 @@ export default function ConditionsSection() {

<View style={styles.information}>
<View style={isOverviewPressed ? styles.overview : styles.overviewHidden}>
<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Importance</Text>
<Text style={styles.descriptionInfo}>
The cervical spine is not stabilized or protected by ribs or other surrounding
structures, so fractures are more common and can be unstable. This creates risk for
potential damage to the spinal cord resulting in quadriplegia and death and could be
made worse by moving patients without proper immobilization
</Text>
</View>

<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Mechanism of Injury</Text>
<BulletList
items={[
"Direct blow to head/neck",
"Axial loading to spine, esp. w/neck in flexion",
]}
/>
</View>

<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Diagnosis</Text>
<BulletList items={["Local pain", "Ecchymosis, and swelling"]} />
</View>

<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Physical Exam</Text>
<BulletList items={["TTP over spinous process or vertebral bodies"]} />
</View>
{emergency?.overview && typeof emergency.overview === "string" && (
<View style={styles.infoSection}>
<Text style={styles.descriptionInfo}>{emergency?.overview}</Text>
</View>
)}

{emergency?.overview && typeof emergency.overview === "object" && (
<View style={styles.infoSection}>
{overviewHeaders.map((header, index) => (
<View key={index}>
<Text style={styles.descriptionTitle}>{header}</Text>
<StringRenderer data={overviewValues[index]} />
</View>
))}
</View>
)}
</View>

<View style={isTreatmentPressed ? styles.howToTreat : styles.howToTreatHidden}>
<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Acute Management</Text>
<BulletList
items={[
"Immobilize with spine board, cervical-collar, and barriers to lateral head movement (or whole body vacuum splint)",
"If this is not available, immobilize by placing hands on patient shoulders and using forearms to immobilize head",
]}
/>
</View>

<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Dispo</Text>
<BulletList items={["Emergency transport to ED for CT (most accurate) +/- XR"]} />
</View>

<View style={styles.infoSection}>
<Text style={styles.descriptionTitle}>Considerations</Text>
<Text style={styles.descriptionInfo}>
If any suspicion for injury, send to ED. However, less likely if the following
criteria are met:
</Text>
<BulletList
items={[
"No cervical spine tenderness",
"Normal alertness/consciousness/GCS 15",
"No major distracting injuries",
"Normal neurologic status (full strength/sensation in all extremities)",
"Ability to actively rotate neck to 45 degrees laterally in both directions",
]}
/>
</View>
{emergency?.treatment && typeof emergency.treatment === "string" && (
<View style={styles.infoSection}>
<Text style={styles.descriptionInfo}>{emergency?.treatment}</Text>
</View>
)}

{emergency?.treatment && typeof emergency.treatment === "object" && (
<View style={styles.infoSection}>
{treatmentHeaders.map((header, index) => (
<View key={index}>
<Text style={styles.descriptionTitle}>{header}</Text>
<StringRenderer data={treatmentValues[index]} />
</View>
))}
</View>
)}

{emergency?.content && typeof emergency.content === "string" && (
<View style={styles.infoSection}>
<Text style={styles.descriptionInfo}>{emergency?.content}</Text>
</View>
)}

{emergency?.content && typeof emergency.content === "object" && (
<View style={styles.infoSection}>
{contentHeaders.map((header, index) => (
<View key={index}>
<Text style={styles.descriptionTitle}>{header}</Text>
<StringRenderer data={contentValues[index]} />
</View>
))}
</View>
)}
</View>
</View>
</ScrollView>
Expand Down
48 changes: 48 additions & 0 deletions dfm-sideline-sidekick-app/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useNavigation } from "@react-navigation/native";
import { useEffect, useState } from "react";
import { Pressable, Text } from "react-native";

import { getEmergency } from "./emergencies";

export default function HomeScreen() {
const navigation = useNavigation();
const [emergency, setEmergency] = useState(null);

useEffect(() => {
async function fetchEmergency() {
try {
//It seems to work on Android and iPad
//Test Case: Cervical Spine Injury - demonstrate recursive rendering
const emergencyObjectId = "65b36d110c9c60394b37f7a1";
//Separate Test Case here: To Be Deleted Emergency - demonstrates textual rendering
//const emergencyObjectId = "65b369a8e8fe96a404d4fd6b";
//Test Case: New Emergency Placeholder Four - demonstrates blank rendering (only title in db)
//const emergencyObjectId = "65c2ef26b87b638ac61beb09";
//Test Case: Cervical Strain - demonstrates simple placeholder headers
//const emergencyObjectId = "65b36f12640d62464e0dd129";
const result = await getEmergency(emergencyObjectId);
if (result.success) {
setEmergency(result.data);
} else {
console.error("Error fetching emergency data:", result.error);
}
} catch (error) {
console.error("Error fetching emergency data:", error);
}
}

void fetchEmergency();
}, []);

const handlePress = () => {
if (emergency !== null) {
navigation.navigate("Conditions", { emergency });
}
};

return (
<Pressable onPress={handlePress}>
<Text>Navigate to ConditionsSection</Text>
</Pressable>
);
}
43 changes: 43 additions & 0 deletions dfm-sideline-sidekick-app/components/StringRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { Text, View } from "react-native";

import styles from "../ConditionSectionStyles";

type StringValue = string | string[] | { [key: string]: StringValue } | undefined;

type Props = {
data: StringValue;
};

const BulletList: React.FC<{ items: string[] }> = ({ items }) => (
<View style={styles.list}>
{items.map((item: string, index: number) => (
<View key={index} style={styles.listItem}>
<Text style={styles.bullet}>{"\u2022"}</Text>
<Text style={styles.itemText}>{item}</Text>
</View>
))}
</View>
);

const StringRenderer: React.FC<Props> = ({ data }) => {
if (typeof data === "string") {
return <Text style={styles.descriptionInfo}>{data}</Text>;
} else if (Array.isArray(data) && data.every((item) => typeof item === "string")) {
return <BulletList items={data} />;
} else if (typeof data === "object") {
return (
<View>
{Object.keys(data).map((key, index) => (
<View key={index}>
<Text style={styles.descriptionTitle}>{key}</Text>
<StringRenderer data={data[key] as StringValue} />
</View>
))}
</View>
);
}
return null;
};

export default StringRenderer;
Loading
Loading