Introduces Unicode-safe base64 encoding/decoding for call details between incidents and call screens. Refactors PageHeader to accept left, center, and right header props for more flexible layouts. Updates call.jsx and register.jsx to use the new header structure, and improves department/unit rendering and modal dropdowns for department selection. Generalizes and modernizes UI code for better maintainability and cross-platform compatibility.
351 lines
No EOL
13 KiB
JavaScript
351 lines
No EOL
13 KiB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import styled from 'styled-components';
|
|
import { useCallFeed } from '@/hooks';
|
|
import { router } from 'expo-router';
|
|
import { Platform, Linking, View, ScrollView, Text, TouchableOpacity } from 'react-native';
|
|
import { StatusBar } from 'expo-status-bar';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import {
|
|
PageHeader,
|
|
PageFooter,
|
|
} from '@/components/generalHelpers.jsx';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { AccidentAndEmergency } from "healthicons-react-native/dist/outline";
|
|
import ActionSheet from 'react-native-actions-sheet';
|
|
|
|
const DepartmentActionSheet = styled(ActionSheet)``;
|
|
|
|
function toBase64Unicode(str) {
|
|
return btoa(new TextEncoder().encode(str).reduce((data, byte) => data + String.fromCharCode(byte), ''));
|
|
}
|
|
|
|
export default function Incidents() {
|
|
const actionSheetRef = useRef(null);
|
|
const callFeed = useCallFeed();
|
|
|
|
const {
|
|
departments,
|
|
callIconMap,
|
|
callDetails,
|
|
callColorSelector,
|
|
formatCallTimePast,
|
|
formatCallDateTime
|
|
} = callFeed;
|
|
|
|
const sortedAndFilteredCalls = callDetails.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
|
.filter((item, index, self) => {
|
|
return index === self.findIndex(i => {
|
|
return `${i?.incident?.incID}${i?.response?.serviceName}` === `${item?.incident?.incID}${item?.response?.serviceName}`
|
|
});
|
|
})?.map(item => { return {...item, timestamp: item?.incident?.incDate} })
|
|
|| [];
|
|
|
|
const {
|
|
departmentTypeMap,
|
|
accountDetails,
|
|
deptList,
|
|
setDeptList,
|
|
selectedDepartment,
|
|
setSelectedDepartment,
|
|
updateSelectedDepartment,
|
|
selectedDepartmentColorPicker,
|
|
} = departments;
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<PageHeader
|
|
centerHeader={<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>}
|
|
/>
|
|
<ScrollView>
|
|
<StatusBar style="dark" />
|
|
<SafeAreaView />
|
|
<View style={{ flexDirection: 'column', padding: 20 }}>
|
|
{sortedAndFilteredCalls?.length ? (
|
|
sortedAndFilteredCalls?.map((callItem, index) => {
|
|
const { incident, address, response, timestamp } = callItem;
|
|
const {
|
|
serviceNumber,
|
|
incDate,
|
|
incNature,
|
|
incNatureCode,
|
|
incNatureCodeDesc,
|
|
status,
|
|
} = incident;
|
|
const {
|
|
streetAddress,
|
|
addressApartment,
|
|
town,
|
|
state,
|
|
locationName,
|
|
} = address;
|
|
const {
|
|
serviceName
|
|
} = response;
|
|
const SelectedIcon = callIconMap[incNature] || AccidentAndEmergency;
|
|
return (
|
|
<TouchableOpacity
|
|
key={`callDetails - ${timestamp}`}
|
|
style={{ paddingBottom: 15 }}
|
|
onPress={() => {
|
|
router.push({
|
|
pathname: '/call',
|
|
params: {
|
|
callDetails: toBase64Unicode(JSON.stringify(callItem))
|
|
}
|
|
})
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
borderRadius: 12,
|
|
elevation: 3,
|
|
backgroundColor: '#fff',
|
|
shadowOffset: { width: 0, height: 0 },
|
|
shadowColor: callColorSelector(
|
|
incNatureCodeDesc,
|
|
incNature,
|
|
status
|
|
),
|
|
shadowOpacity: 1,
|
|
shadowRadius: 5,
|
|
padding: 10,
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: 'column' }}>
|
|
<View key="callDateAndTime"
|
|
style={{
|
|
paddingBottom: 6,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between'
|
|
}}
|
|
>
|
|
<Text style={{ fontSize: 12 }}>{formatCallDateTime(incDate)}</Text>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ fontSize: 12 }}>{formatCallTimePast(incDate)}</Text>
|
|
{status === 'CLOSED' ? (
|
|
<Ionicons
|
|
name="lock-closed-outline"
|
|
color='red'
|
|
style={{
|
|
shadowColor: 'black',
|
|
shadowOffset: 0,
|
|
shadowOpacity: 1,
|
|
shadowRadius: 10
|
|
}}
|
|
/>
|
|
) : null}
|
|
</View>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<SelectedIcon
|
|
color={callColorSelector(
|
|
incNatureCodeDesc,
|
|
incNature,
|
|
status
|
|
)}
|
|
opacity={0.3}
|
|
width={56}
|
|
height={56}
|
|
/>
|
|
<View style={{ flex: 1, flexDirection: 'column', alignItems: 'flex-start', paddingHorizontal: 2, maxWidth: '80%' }}>
|
|
{locationName ? (
|
|
<Text
|
|
style={{
|
|
color: 'black',
|
|
fontWeight: 600,
|
|
fontSize: 16
|
|
}}
|
|
>
|
|
{`${locationName}`}
|
|
</Text>
|
|
) : (
|
|
<View style={{ flexDirection: 'row' }}>
|
|
<Text
|
|
style={[{
|
|
color: 'black',
|
|
fontSize: 12,
|
|
fontWeight: 600
|
|
}]}
|
|
>
|
|
{`${streetAddress?.split(',')[0]}`}
|
|
</Text>
|
|
{addressApartment ? (
|
|
<Text
|
|
style={[{
|
|
color: 'black',
|
|
fontSize: 12,
|
|
fontWeight: 600
|
|
}]}
|
|
>
|
|
{` - ${addressApartment}`}
|
|
</Text>
|
|
) : null}
|
|
<Text
|
|
style={[{
|
|
color: 'black',
|
|
fontSize: 12,
|
|
fontWeight: 600
|
|
}]}
|
|
>
|
|
{` ${town}, ${state}`}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
<Text
|
|
style={{
|
|
color: 'black',
|
|
fontWeight: 600,
|
|
fontSize: 16
|
|
}}
|
|
>
|
|
{`${incNature}`}
|
|
</Text>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
<Text
|
|
style={{
|
|
color: 'black',
|
|
fontSize: 12,
|
|
textShadowColor: callColorSelector(
|
|
incNatureCode,
|
|
incNature,
|
|
status
|
|
),
|
|
textShadowRadius: 1
|
|
}}
|
|
>
|
|
{`${incNatureCodeDesc}`}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<View style={{ padding: 0 }}>
|
|
<View>
|
|
<Ionicons name="chevron-forward-outline" />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
<View style={{ paddingTop: 5, flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
<Text
|
|
style={{
|
|
fontSize: 12,
|
|
}}
|
|
>
|
|
Service: {serviceName}
|
|
</Text>
|
|
<Text
|
|
style={{
|
|
fontSize: 12,
|
|
textAlign: 'right'
|
|
}}
|
|
>
|
|
{`Incident #: ${serviceNumber}`}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
)
|
|
})) : (
|
|
<Text>There are no Calls</Text>
|
|
)}
|
|
</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>
|
|
)
|
|
} |