Feature/notification system #26

Open
mattdimegs wants to merge 34 commits from feature/notification-system into main
4 changed files with 86 additions and 63 deletions
Showing only changes of commit cb1758fb55 - Show all commits

View file

@ -1,8 +1,8 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useCallFeed } from '../hooks/useCallFeed'; import { useCallFeed } from '../hooks/useCallFeed';
import { Platform, Linking, View, ScrollView, Text, TouchableOpacity } from 'react-native'; import { Platform, Linking, View, ScrollView, Text, TouchableOpacity } from 'react-native';
import { useLocalSearchParams, router } from 'expo-router'; import { useLocalSearchParams } from 'expo-router';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@ -11,7 +11,7 @@ import { Phone } from "healthicons-react-native/dist/filled";
import { import {
PageHeader, PageHeader,
PageFooter, PageFooter,
} from '../components/generalHelpers.jsx'; } from '@/components/generalHelpers.jsx';
import ActionSheet from 'react-native-actions-sheet'; import ActionSheet from 'react-native-actions-sheet';
const DepartmentActionSheet = styled(ActionSheet)``; const DepartmentActionSheet = styled(ActionSheet)``;

View file

@ -28,13 +28,13 @@ export default function Incidents() {
formatCallDateTime formatCallDateTime
} = callFeed; } = callFeed;
const sortedAndFilteredCalls = callDetails const sortedAndFilteredCalls = callDetails.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.filter((item, index, self) => { .filter((item, index, self) => {
return index === self.findIndex(i => { return index === self.findIndex(i => {
return `${i?.data?.Incident?.IncID}${i?.data?.Response?.ServiceName}` === `${item?.data?.Incident?.IncID}${item?.data?.Response?.ServiceName}` return `${i?.incident?.incID}${i?.response?.serviceName}` === `${item?.incident?.incID}${item?.response?.serviceName}`
}); });
}); })?.map(item => { return {...item, timestamp: item?.incident?.incDate} })
|| [];
const { const {
departmentTypeMap, departmentTypeMap,
@ -93,31 +93,30 @@ export default function Incidents() {
<View style={{ flexDirection: 'column', padding: 20 }}> <View style={{ flexDirection: 'column', padding: 20 }}>
{sortedAndFilteredCalls?.length ? ( {sortedAndFilteredCalls?.length ? (
sortedAndFilteredCalls?.map((callItem, index) => { sortedAndFilteredCalls?.map((callItem, index) => {
const { data: call, timestamp } = callItem; const { incident, address, response, timestamp } = callItem;
const { Incident, Address, Response } = call;
const { const {
ServiceNumber, serviceNumber,
IncDate, incDate,
IncNature, incNature,
IncNatureCode, incNatureCode,
IncNatureCodeDesc, incNatureCodeDesc,
Status, status,
} = Incident; } = incident;
const { const {
StreetAddress, streetAddress,
AddressApartment, addressApartment,
Town, town,
State, state,
LocationName, locationName,
} = Address; } = address;
const { const {
ServiceName serviceName
} = Response; } = response;
const SelectedIcon = callIconMap[IncNature] || AccidentAndEmergency; const SelectedIcon = callIconMap[incNature] || AccidentAndEmergency;
return ( return (
<TouchableOpacity <TouchableOpacity
key={`callDetails - ${timestamp}`} key={`callDetails - ${timestamp}`}
style={{ padding: 2 }} style={{ paddingBottom: 15 }}
onPress={() => { onPress={() => {
router.push({ router.push({
pathname: '/call', pathname: '/call',
@ -134,9 +133,9 @@ export default function Incidents() {
backgroundColor: '#fff', backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 }, shadowOffset: { width: 0, height: 0 },
shadowColor: callColorSelector( shadowColor: callColorSelector(
IncNatureCode, incNatureCodeDesc,
IncNature, incNature,
Status status
), ),
shadowOpacity: 1, shadowOpacity: 1,
shadowRadius: 5, shadowRadius: 5,
@ -151,10 +150,10 @@ export default function Incidents() {
justifyContent: 'space-between' justifyContent: 'space-between'
}} }}
> >
<Text style={{ fontSize: 12 }}>{formatCallDateTime(IncDate)}</Text> <Text style={{ fontSize: 12 }}>{formatCallDateTime(incDate)}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ fontSize: 12 }}>{formatCallTimePast(IncDate)}</Text> <Text style={{ fontSize: 12 }}>{formatCallTimePast(incDate)}</Text>
{Status === 'CLOSED' ? ( {status === 'CLOSED' ? (
<Ionicons <Ionicons
name="lock-closed-outline" name="lock-closed-outline"
color='red' color='red'
@ -171,16 +170,16 @@ export default function Incidents() {
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
<SelectedIcon <SelectedIcon
color={callColorSelector( color={callColorSelector(
IncNatureCode, incNatureCodeDesc,
IncNature, incNature,
Status status
)} )}
opacity={0.3} opacity={0.3}
width={56} width={56}
height={56} height={56}
/> />
<View style={{ flexDirection: 'column' }}> <View style={{ flexDirection: 'column' }}>
{LocationName ? ( {locationName ? (
<Text <Text
style={{ style={{
color: 'black', color: 'black',
@ -188,7 +187,7 @@ export default function Incidents() {
fontSize: 16 fontSize: 16
}} }}
> >
{`${LocationName}`} {`${locationName}`}
</Text> </Text>
) : ( ) : (
<View style={{ flexDirection: 'row' }}> <View style={{ flexDirection: 'row' }}>
@ -199,9 +198,9 @@ export default function Incidents() {
fontWeight: 600 fontWeight: 600
}]} }]}
> >
{`${StreetAddress}`} {`${streetAddress?.split(',')[0]}`}
</Text> </Text>
{AddressApartment ? ( {addressApartment ? (
<Text <Text
style={[{ style={[{
color: 'black', color: 'black',
@ -209,7 +208,7 @@ export default function Incidents() {
fontWeight: 600 fontWeight: 600
}]} }]}
> >
{` - ${AddressApartment}`} {` - ${addressApartment}`}
</Text> </Text>
) : null} ) : null}
<Text <Text
@ -219,7 +218,7 @@ export default function Incidents() {
fontWeight: 600 fontWeight: 600
}]} }]}
> >
{` ${Town}, ${State}`} {` ${town}, ${state}`}
</Text> </Text>
</View> </View>
)} )}
@ -230,7 +229,7 @@ export default function Incidents() {
fontSize: 16 fontSize: 16
}} }}
> >
{`${IncNature}`} {`${incNature}`}
</Text> </Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}> <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text <Text
@ -238,14 +237,14 @@ export default function Incidents() {
color: 'black', color: 'black',
fontSize: 12, fontSize: 12,
textShadowColor: callColorSelector( textShadowColor: callColorSelector(
IncNatureCode, incNatureCode,
IncNature, incNature,
Status status
), ),
textShadowRadius: 1 textShadowRadius: 1
}} }}
> >
{`${IncNatureCodeDesc}`} {`${incNatureCodeDesc}`}
</Text> </Text>
</View> </View>
</View> </View>
@ -261,7 +260,7 @@ export default function Incidents() {
fontSize: 12, fontSize: 12,
}} }}
> >
Service: {ServiceName} Service: {serviceName}
</Text> </Text>
<Text <Text
style={{ style={{
@ -269,7 +268,7 @@ export default function Incidents() {
textAlign: 'right' textAlign: 'right'
}} }}
> >
{`Incident #: ${ServiceNumber}`} {`Incident #: ${serviceNumber}`}
</Text> </Text>
</View> </View>
</View> </View>

View file

@ -86,16 +86,16 @@ const formatCallDateTime = (callValue) => {
const getIncidents = async (departments, incidentStatus) => { const getIncidents = async (departments, incidentStatus) => {
let response; let response;
try { try {
response = await fetch(`${process.env.EXPO_PUBLIC_DATABASE_URL}/api/getRecentCalls`, { response = await fetch(`${process.env.EXPO_PUBLIC_DATABASE_URL}/getRecentCalls/${5}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json",
apiKey: process.env.EXPO_PUBLIC_DATABASE_API_KEY apiKey: process.env.EXPO_PUBLIC_DATABASE_API_KEY
}, },
body: JSON.stringify({ // body: JSON.stringify({
departments, // callCount: 25,
status: incidentStatus, // departments,
}), // status: incidentStatus,
// }),
}); });
} catch (e) { } catch (e) {
console.error("Failed to fetch initial calls:", e); console.error("Failed to fetch initial calls:", e);
@ -114,9 +114,12 @@ export const useCallFeed = () => {
const [selectedStatus, setSelectedStatus] = useState({id: 'all', value: 'All Incidents'}); const [selectedStatus, setSelectedStatus] = useState({id: 'all', value: 'All Incidents'});
useEffect(() => { useEffect(() => {
const incidents = getIncidents(InitList?.map((dept) => { return dept?.id }), selectedStatus?.id); async function fetchData() {
setAllCalls(incidents); const incidents = await getIncidents(InitList?.map((dept) => { return dept?.id }), selectedStatus?.id);
setInit(false); setAllCalls(incidents);
setInit(false);
}
fetchData();
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -129,14 +132,17 @@ export const useCallFeed = () => {
}, [lastMessage]); }, [lastMessage]);
useEffect(() => { useEffect(() => {
if (!init) { async function fetchData() {
const incidents = getIncidents(InitList?.map((dept) => { return dept?.id }), selectedStatus?.id); const incidents = await getIncidents(InitList?.map((dept) => { return dept?.id }), selectedStatus?.id);
setAllCalls(incidents); setAllCalls(incidents);
} }
if (!init) {
fetchData();
}
}, [selectedStatus]); }, [selectedStatus]);
const callColorSelector = (callAcuity, cardiacArrestCall, status) => { const callColorSelector = (callAcuity, cardiacArrestCall, status) => {
if (status === "CLOSED") { if (status?.toLowerCase() === "closed") {
return "#0000CD"; return "#0000CD";
} else if (CriticalCallList.includes(cardiacArrestCall)) { } else if (CriticalCallList.includes(cardiacArrestCall)) {
return "#8B0000"; return "#8B0000";

View file

@ -12,14 +12,32 @@ const accountDetails = {
"CARDIAC ARREST|DEATH", "CARDIAC ARREST|DEATH",
], ],
"HighCallList": [ "HighCallList": [
"ALS" "EMS ALS PRIORITY (ALS)",
"EMS Advanced Life Support life threatening response (ALS)",
"EMS ALS Intercept Required",
"EMS ALS Response Cardiac Related",
"EMS ALS Response Respiratory Distress",
"EMS ALS Response Stroke Alert",
"EMS Basic Life Support with Paramedic Assist (BLS)",
"EMS ALS Tiered Response with Fire",
"EMS Response Behavioral Emergency (ALS)",
"EMS Response Obstetric Emergency (ALS)",
"EMS ALS Response Overdose / Poisoning",
"EMS ALS Priority Interfacility Transfer",
"EMS Response Unresponsive / Unconscious Patient (ALS)"
], ],
"MediumCallList": [ "MediumCallList": [
"ALS-STANDARD", "ALS-STANDARD",
"BLS-PRIORITY" "BLS-PRIORITY",
"EMS BLS Priority Response",
"EMS BLS Response Fall Injury"
], ],
"LowCallList": [ "LowCallList": [
"BLS-STANDARD" "EMS Standard Basic Life Support Response (BLS)",
"EMS BLS Non-Emergency Transport",
"EMS BLS Standby Event Coverage",
"EMS Public Assist Lift Only (BLS)",
"EMS BLS Scheduled Interfacility Transport"
] ]
}, },
"InitList": [ "InitList": [