Tones/app/call.jsx

839 lines
30 KiB
React
Raw Normal View History

2025-08-11 19:34:16 +00:00
import React, { useRef } from 'react';
2025-04-19 02:50:53 +00:00
import styled from 'styled-components';
import { router } from 'expo-router';
import { useCallFeed } from '../hooks/useCallFeed';
import { Platform, Linking, View, ScrollView, Text, TouchableOpacity } from 'react-native';
2025-08-11 19:34:16 +00:00
import { useLocalSearchParams } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
import { AccidentAndEmergency } from "healthicons-react-native/dist/outline";
import { Phone } from "healthicons-react-native/dist/filled";
import {
PageHeader,
PageFooter,
2025-08-11 19:34:16 +00:00
} from '@/components/generalHelpers.jsx';
2025-04-19 02:50:53 +00:00
import ActionSheet from 'react-native-actions-sheet';
const DepartmentActionSheet = styled(ActionSheet)``;
function fromBase64Unicode(str) {
return new TextDecoder().decode(Uint8Array.from(atob(str), c => c.charCodeAt(0)));
}
2025-04-19 16:40:07 +00:00
export default function Call() {
const { callDetails } = useLocalSearchParams();
const actionSheetRef = useRef(null);
const callFeed = useCallFeed(true);
const {
departments,
callIconMap,
callColorSelector,
formatCallTimePast,
formatCallDateTime
} = callFeed;
const {
departmentTypeMap,
accountDetails,
selectedDepartment,
setSelectedDepartment,
updateSelectedDepartment,
selectedDepartmentColorPicker,
deptList,
} = departments;
const decoded = fromBase64Unicode(callDetails);
const { incident, address, person, response } = JSON.parse(decoded);
const { CallThemes } = accountDetails;
const {
incID,
incNumber,
jurisdictionNumber,
serviceNumber,
serviceID,
incDate,
incNature,
incNatureCode,
incNatureCodeDesc,
notes,
status,
origin,
} = incident;
const {
streetAddress,
addressApartment,
town,
state,
zipCode,
latitude,
longitude,
county,
intersection1,
intersection2,
locationName,
weatherCondition,
} = address;
const {
name,
age,
gender,
statement,
conscious,
breathing,
callBackNumber,
} = person;
const {
units
} = response;
2025-04-19 06:36:25 +00:00
const SelectedIcon = callIconMap[incNature] || AccidentAndEmergency;
const ownDepartmentResponse = units?.map((unit) => {
if (unit?.department === selectedDepartment?.dept ||
selectedDepartment?.addDepts?.includes(unit?.department)) {
return unit;
}
return null;
})?.filter((filterItem) => {
return filterItem;
});
const mutualAidDepartmentResponse = units?.map((unit) => {
if (unit?.department !== selectedDepartment?.dept &&
!selectedDepartment?.addDepts?.includes(unit?.department)) {
return unit;
}
return null;
})?.filter((filterItem) => {
return filterItem;
});;
const formatResponseTimes = (time) => {
if (time === null || time === undefined || time === '') {
return '';
}
const initTime = new Date(time);
const hours = initTime.getHours().toString().padStart(2, '0');
const minutes = initTime.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
const openMaps = (latitude, longitude) => {
const daddr = `${latitude},${longitude}`;
const company = Platform.OS === "ios" ? "apple" : "google";
Linking.openURL(`http://maps.${company}.com/maps?daddr=${daddr}`);
};
const callNumber = (number) => {
const formattedNumber = number.replace(/[()\-\s]/g, '');
Linking.openURL(`tel:${formattedNumber}`);
};
return (
<React.Fragment>
<PageHeader
leftHeader={ <View style={{ flex: 1, alignItems: 'flex-start' }}>
<TouchableOpacity
onPress={router.back}
style={{ flexDirection: 'row', alignItems: 'center' }}
>
<Ionicons name="chevron-back-outline" size={22} color="red" style={{ paddingLeft: 10 }} />
<Text style={{ color: 'red', fontWeight: 600 }}>Back to Incidents</Text>
</TouchableOpacity>
</View>}
centerHeader={<View style={{ flex: 1, alignItems: 'center' }}>
<TouchableOpacity
style={{
borderRadius: 6,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: '#333',
shadowOpacity: .8,
shadowRadius: 2,
paddingHorizontal: 10,
paddingVertical: 2,
}}
onPress={() => {
actionSheetRef.current?.show();
}}
>
<Text
style={{
color: selectedDepartmentColorPicker(selectedDepartment?.type),
fontWeight: 600,
fontSize: 14
}}
>
{selectedDepartment?.deptAbv}
</Text>
</TouchableOpacity>
</View>}
/>
<ScrollView showsVerticalScrollIndicator={false}>
<StatusBar style="dark" />
<SafeAreaView />
<View style={{ flexDirection: 'column', padding: 20 }}>
<View key="callDateAndTime"
style={{
paddingBottom: 6,
paddingLeft: 10,
paddingRight: 10,
flexDirection: 'row',
justifyContent: 'space-between'
}}
>
<Text style={{ fontSize: 12 }}>{formatCallDateTime(incDate)}</Text>
<Text style={{ fontSize: 12 }}>{formatCallTimePast(incDate)}</Text>
</View>
<View key="callDetails" style={{ padding: 2 }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: callColorSelector(
incNatureCode,
incNature,
status
),
shadowOpacity: 1,
shadowRadius: 5,
padding: 10,
}}
>
<View style={{ flexDirection: 'column' }}>
<View style={{ flexDirection: 'row', alignItems: 'center', maxWidth: '90%' }}>
<SelectedIcon
color={callColorSelector(
incNatureCode,
incNature,
status
)}
opacity={0.3}
width={56}
height={56}
/>
<View style={{ flexDirection: 'column', maxWidth: '90%' }}>
<Text
style={{
color: 'black',
fontWeight: 600,
fontSize: 16
}}
>
{`${incNature}`}
</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', maxWidth: '90%' }}>
<Text
style={{
color: 'black',
fontSize: 12,
textShadowColor: callColorSelector(
incNatureCode,
incNature,
status
),
textShadowRadius: 1
}}
>
{`${incNatureCodeDesc}`}
</Text>
</View>
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
{status.toLowerCase() === 'closed' ? (
<Text
style={{
fontSize: 12,
fontWeight: 600,
color: '#'
}}
>
This Incident is Closed.
</Text>
) : <Text></Text>}
<Text
style={{
fontSize: 12,
textAlign: 'right'
}}
>
{`Incident #: ${serviceNumber}`}
</Text>
</View>
</View>
</View>
</View>
<View style={{ margin: 6 }} />
<View key="callLocation" style={{ padding: 2 }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
padding: 10,
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'top',
justifyContent: 'space-between'
}}
>
<View style={{ flexDirection: 'column', maxWidth: '70%' }}>
{locationName ? (
<Text
style={{
color: 'black',
fontWeight: 600,
fontSize: 16
}}
>
{`${locationName}`}
</Text>
) : null}
<TouchableOpacity
onLongPress={() => {
return openMaps(latitude, longitude);
}}
>
<Text
style={[{
color: 'black',
}, locationName ? {} : { fontSize: 12, fontWeight: 600 }]}
>
{`${streetAddress}`}
</Text>
<Text
style={[{
color: 'black',
}, locationName ? {} : { fontSize: 12, fontWeight: 600 }]}
>
{`${town}, ${state}`}
</Text>
</TouchableOpacity>
{addressApartment ? (
<View>
<View style={{ margin: 10 }} />
<Text
style={[{
color: 'black',
}, locationName ? {} : { fontSize: 12, fontWeight: 600 }]}
>
{`${addressApartment}`}
</Text>
</View>
) : null}
</View>
<View style={{ flexDirection: 'column' }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
padding: 25,
}}
>
<Text style={{ fontSize: 12 }}>Map</Text>
</View>
<TouchableOpacity
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#ECEDEE',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
marginTop: 6,
paddingLeft: 6,
paddingVertical: 6,
flexDirection: 'row',
justifyContent: 'center'
}}
onPress={() => {
return openMaps(latitude, longitude);
}}
>
<Text style={{ fontSize: 10 }}>Nav</Text>
<Ionicons name="chevron-forward-outline" />
</TouchableOpacity>
</View>
</View>
</View>
</View>
<View style={{ margin: 6 }} />
<View key="callCrossStreets"
style={{
padding: 2,
flexDirection: 'row',
justifyContent: 'space-evenly',
marginHorizontal: 2,
}}
>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
paddingVertical: 14,
width: "48%"
}}
>
<Text
style={{
fontSize: 12,
fontWeight: 600,
textAlign: 'center'
}}
>
{intersection1}
</Text>
</View>
<View style={{ paddingHorizontal: "2%" }} />
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
paddingVertical: 14,
width: "48%"
}}
>
<Text
style={{
fontSize: 12,
fontWeight: 600,
textAlign: 'center'
}}
>
{intersection2}
</Text>
</View>
</View>
<View style={{ margin: 6 }} />
<View key="callerInformation" style={{ padding: 2 }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
padding: 10,
}}
>
{selectedDepartment?.supervisor ? (
<View>
<View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginHorizontal: 10
}}
>
<View style={{ flexDirection: 'column' }}>
<Text
style={{
color: 'black',
fontWeight: 600,
fontSize: 16
}}
>
{`${name}`}
</Text>
<TouchableOpacity
onLongPress={() => {
return callNumber(callBackNumber);
}}
>
<Text
style={{ color: 'black', fontSize: 12 }}
>
{`${callBackNumber}`}
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onLongPress={() => {
return callNumber(callBackNumber);
}}
>
<Phone
color='blue'
opacity={0.3}
width={32}
height={32}
/>
</TouchableOpacity>
</View>
</View>
<View
style={{
marginVertical: 5,
height: 1,
width: '100%',
backgroundColor: 'grey'
}}
/>
</View>
) : null}
<View style={{ alignItems: 'center' }}>
<View style={{ flexDirection: 'row' }}>
<Text style={{ fontSize: 14 }}>
{`${age} `}
</Text>
<Text style={{ fontSize: 14 }}>
{`${gender} - `}
</Text>
<Text style={{ fontSize: 14 }}>
{`Conscious: ${conscious} | `}
</Text>
<Text style={{ fontSize: 14 }}>
{`Breathing: ${breathing}`}
</Text>
</View>
<Text style={{ fontSize: 12 }}>
{`${statement}`}
</Text>
</View>
</View>
</View>
<View style={{ alignItems: 'center' }}>
<View
style={{
marginVertical: 10,
height: 2,
width: '95%',
backgroundColor: 'grey'
}}
/>
</View>
<View key="respondingUnitInformation" style={{ padding: 2 }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
padding: 10,
}}
2024-10-06 21:55:18 +00:00
>
<View style={{ flexDirection: 'column' }}>
{units?.length > 0 ? (
<View>
{ownDepartmentResponse?.length > 0 ? (
<View>
<View>
<View style={{ flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center' }}>
<Text style={{ fontWeight: '600', textAlign: 'center', flex: 1 }}>Units</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Disp</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Resp</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Arr</Text>
{selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
<Text style={{ textAlign: 'center', flex: 1 }}>Trans</Text>
) : null}
<Text style={{ textAlign: 'center', flex: 1 }}>BIS</Text>
</View>
<View
style={{
marginVertical: 5,
height: 1,
width: '95%',
backgroundColor: 'grey',
alignSelf: 'center'
}}
/>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}}
>
{ownDepartmentResponse?.map((unit) => {
return (
<View key={unit?.unit}
style={{
flex: 1,
alignSelf: 'stretch',
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'center'
}}
>
<Text
2024-10-06 21:55:18 +00:00
style={{
fontSize: 12,
fontWeight: 600,
textAlign: 'center',
flex: 1
2024-10-06 21:55:18 +00:00
}}
>
{unit?.unit}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.dispatched)}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.responding)}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.onScene)}
</Text>
{selectedDepartment?.type === 'EMS' ||
selectedDepartment?.type === 'Rescue' ? (
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.transporting)}
</Text>
) : null}
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.inService)}
</Text>
</View>
)
})}
</View>
</View>
</View>
) : (
<View style={{ alignItems: 'center' }}>
<Text
style={{
textAlign: 'center'
}}
>
{`No ${selectedDepartment?.dept} Units Responding`}
</Text>
</View>
)}
{mutualAidDepartmentResponse?.length > 0 ? (
<View>
<View
style={{
marginVertical: 5,
height: 2,
width: '100%',
backgroundColor: 'grey',
}}
/>
<View style={{ flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center' }}>
<Text style={{ fontWeight: '600', textAlign: 'center', flex: 1 }}>M/A</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Disp</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Resp</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>Arr</Text>
{selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
<Text style={{ textAlign: 'center', flex: 1 }}>Trans</Text>
) : null}
<Text style={{ textAlign: 'center', flex: 1 }}>BIS</Text>
</View>
<View
style={{
marginVertical: 5,
height: 1,
width: '95%',
backgroundColor: 'grey',
alignSelf: 'center'
}}
/>
{mutualAidDepartmentResponse?.map((unit) => {
return (
<View key={unit?.unit}
style={{
flex: 1,
alignSelf: 'stretch',
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'center'
}}
>
<Text
style={{
fontSize: 12,
fontWeight: 600,
textAlign: 'center',
flex: 1
}}
>
{unit?.unit}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.dispatched)}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.responding)}
</Text>
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.onScene)}
</Text>
{selectedDepartment?.type === 'EMS' ||
selectedDepartment?.type === 'Rescue' ? (
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.transporting)}
</Text>
) : null}
<Text style={{ textAlign: 'center', flex: 1 }}>
{formatResponseTimes(unit?.inService)}
</Text>
2024-10-06 21:55:18 +00:00
</View>
)
})}
</View>
) : null}
</View>
) : (
<View style={{ alignItems: 'center' }}>
<Text>No Units Responding</Text>
</View>
)}
</View>
</View>
</View>
<View style={{ margin: 6 }} />
<Text
style={{
color: 'grey',
fontWeight: 600,
paddingLeft: 10
}}
>
Incident Notes
</Text>
<View style={{ margin: 2 }} />
<View key="incidentNotes" style={{ padding: 2 }}>
<View
style={{
borderRadius: 12,
elevation: 3,
backgroundColor: '#fff',
shadowOffset: { width: 0, height: 0 },
shadowColor: 'grey',
shadowOpacity: 1,
shadowRadius: 1,
padding: 10,
minHeight: 200
}}
>
{notes?.split('\n').map((note, index) => (
<View key={`notes-${index}`}>
<Text>{note}</Text>
{index < notes.split('\n').length - 1 && (
<View
style={{
height: 1,
backgroundColor: 'grey',
marginVertical: 10
}}
/>
)}
2024-10-06 21:55:18 +00:00
</View>
))}
</View>
</View>
</View>
</ScrollView>
<PageFooter>
<View
style={{
flexDirection: 'column',
height: 100,
alignItems: 'center',
justifyContent: 'flex-start',
paddingTop: 7
}}
/>
</PageFooter>
<DepartmentActionSheet
ref={actionSheetRef}
gestureEnabled
containerStyle={{
height: "50%",
width: "100%",
backgroundColor: '#ECEDEE',
}}
>
<View style={{ flexDirection: 'column', padding: 20 }}>
{deptList?.map((item) => {
return (
<View style={{ padding: 2 }} key={item?.deptId}>
<TouchableOpacity
style={{
borderRadius: 6,
elevation: 3,
backgroundColor: item?.selected ? 'grey' : '#fff',
shadowOffset: { width: 1, height: 1 },
shadowColor: '#333',
shadowOpacity: 0.3,
shadowRadius: 2,
marginHorizontal: 20,
marginVertical: 6,
padding: 10
}}
onPress={() => {
actionSheetRef.current?.hide();
return updateSelectedDepartment(
selectedDepartment?.deptId,
item?.deptId
)
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text
style={{
color: selectedDepartmentColorPicker(
item?.type
),
fontWeight: 600,
fontSize: '16'
}}
>
{item?.dept}
</Text>
{item?.primary ? <Ionicons name="star" size={16} color="yellow" style={{
paddingLeft: 20,
shadowColor: '#333',
shadowOffset: 1,
shadowOpacity: 1,
shadowRadius: 6
}} /> : null}
</View>
<Text>{`${item?.deptAbv} - ${departmentTypeMap[item?.type]}`}</Text>
</TouchableOpacity>
</View>
);
})}
</View>
</DepartmentActionSheet>
</React.Fragment>
);
}