diff --git a/app/call.jsx b/app/call.jsx
index ff695a4..7c98d99 100644
--- a/app/call.jsx
+++ b/app/call.jsx
@@ -1,5 +1,6 @@
import React, { useRef } from 'react';
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';
import { useLocalSearchParams } from 'expo-router';
@@ -16,816 +17,823 @@ 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)));
+}
+
export default function Call() {
- const { callDetails } = useLocalSearchParams();
- const actionSheetRef = useRef(null);
- const callFeed = useCallFeed();
+ const { callDetails } = useLocalSearchParams();
+ const actionSheetRef = useRef(null);
+ const callFeed = useCallFeed(true);
- const {
- departments,
- callIconMap,
- callColorSelector,
- formatCallTimePast,
- formatCallDateTime
- } = callFeed;
+ const {
+ departments,
+ callIconMap,
+ callColorSelector,
+ formatCallTimePast,
+ formatCallDateTime
+ } = callFeed;
- const {
- departmentTypeMap,
- accountDetails,
- selectedDepartment,
- setSelectedDepartment,
- updateSelectedDepartment,
- selectedDepartmentColorPicker,
- deptList,
- } = departments;
+ const {
+ departmentTypeMap,
+ accountDetails,
+ selectedDepartment,
+ setSelectedDepartment,
+ updateSelectedDepartment,
+ selectedDepartmentColorPicker,
+ deptList,
+ } = departments;
- const decoded = atob(callDetails);
+ 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;
+ 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;
- const SelectedIcon = callIconMap[IncNature] || AccidentAndEmergency;
+ 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 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 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) {
- 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 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 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 (
-
-
- {
+ const formattedNumber = number.replace(/[()\-\s]/g, '');
+ Linking.openURL(`tel:${formattedNumber}`);
+ };
+
+ return (
+
+
+
+
+ Back to Incidents
+
+ }
+ centerHeader={
+ {
+ actionSheetRef.current?.show();
+ }}
+ >
+
+ {selectedDepartment?.deptAbv}
+
+
+ }
+ />
+
+
+
+
+
+ {formatCallDateTime(incDate)}
+ {formatCallTimePast(incDate)}
+
+
+
-
+
+
+
+
+ {`${incNature}`}
+
+
+
+ {`${incNatureCodeDesc}`}
+
+
+
+
+
+ {status.toLowerCase() === 'closed' ? (
+
+ This Incident is Closed.
+
+ ) : }
+
+ {`Incident #: ${serviceNumber}`}
+
+
+
+
+
+
+
+
+
+
+ {locationName ? (
+
+ {`${locationName}`}
+
+ ) : null}
+ {
+ return openMaps(latitude, longitude);
+ }}
+ >
+
+ {`${streetAddress}`}
+
+
+ {`${town}, ${state}`}
+
+
+ {addressApartment ? (
+
+
+
+ {`${addressApartment}`}
+
+
+ ) : null}
+
+
+
+ Map
+
+ {
- actionSheetRef.current?.show();
+ return openMaps(latitude, longitude);
}}
- >
-
- {selectedDepartment?.deptAbv}
-
-
+ >
+ Nav
+
+
+
+
-
-
-
-
-
-
- {formatCallDateTime(IncDate)}
- {formatCallTimePast(IncDate)}
-
-
-
-
-
-
-
-
- {`${IncNature}`}
-
-
-
- {`${IncNatureCodeDesc}`}
-
-
-
-
-
- {Status === 'CLOSED' ? (
-
- This Incident is Closed.
-
- ) : }
-
- {`Incident #: ${ServiceNumber}`}
-
-
-
-
-
-
-
-
-
-
- {LocationName ? (
-
- {`${LocationName}`}
-
- ) : null }
- {
- return openMaps(Latitude, Longitude);
- }}
- >
-
- {`${StreetAddress}`}
-
-
- {`${Town}, ${State}`}
-
-
- {AddressApartment ? (
-
-
-
- {`${AddressApartment}`}
-
-
- ) : null}
-
-
-
- Map
-
- {
- return openMaps(Latitude, Longitude);
- }}
- >
- Nav
-
-
-
-
-
-
-
-
-
-
- {Intersection1}
-
-
-
-
-
- {Intersection2}
-
-
-
-
-
-
- {selectedDepartment?.supervisor ? (
-
-
-
-
-
- {`${Name}`}
-
- {
- return callNumber(CallBackNumber);
- }}
- >
-
- {`${CallBackNumber}`}
-
-
-
- {
- return callNumber(CallBackNumber);
- }}
- >
-
-
-
-
-
-
- ) : null }
-
-
-
- {`${Age} `}
-
-
- {`${Gender} - `}
-
-
- {`Conscious: ${Conscious} | `}
-
-
- {`Breathing: ${Breathing}`}
-
-
-
- {`${Statement}`}
-
-
-
-
-
-
-
-
-
-
- {Units?.length > 0 ? (
-
- {ownDepartmentResponse?.length > 0 ? (
-
-
-
- Units
- Disp
- Resp
- Arr
- {selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
- Trans
- ) : null}
- BIS
-
-
-
- {ownDepartmentResponse?.map((unit) => {
- return (
-
-
- {unit?.Unit}
-
-
- {formatResponseTimes(unit?.Dispatched)}
-
-
- {formatResponseTimes(unit?.Responding)}
-
-
- {formatResponseTimes(unit?.OnScene)}
-
- {selectedDepartment?.type === 'EMS' ||
- selectedDepartment?.type === 'Rescue' ? (
-
- {formatResponseTimes(unit?.Transporting)}
-
- ) : null }
-
- {formatResponseTimes(unit?.InService)}
-
-
- )
- })}
-
-
-
- ) : (
-
-
- {`No ${selectedDepartment?.dept} Units Responding`}
-
-
- ) }
- {mutualAidDepartmentResponse?.length > 0 ? (
-
-
-
- M/A
- Disp
- Resp
- Arr
- {selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
- Trans
- ) : null}
- BIS
-
-
- {mutualAidDepartmentResponse?.map((unit) => {
- return (
-
-
- {unit?.Unit}
-
-
- {formatResponseTimes(unit?.Dispatched)}
-
-
- {formatResponseTimes(unit?.Responding)}
-
-
- {formatResponseTimes(unit?.OnScene)}
-
- {selectedDepartment?.type === 'EMS' ||
- selectedDepartment?.type === 'Rescue' ? (
-
- {formatResponseTimes(unit?.Transporting)}
-
- ) : null }
-
- {formatResponseTimes(unit?.InService)}
-
-
- )
- })}
-
- ) : null }
-
- ) : (
-
- No Units Responding
-
- )}
-
-
-
-
-
- Incident Notes
-
-
-
-
- {Notes?.split('\n').map((note, index) => (
-
- {note}
- {index < Notes.split('\n').length - 1 && (
-
- )}
-
- ))}
-
-
-
-
-
-
-
-
+
+
+
-
- {deptList?.map((item) => {
- return (
-
- {
- actionSheetRef.current?.hide();
- return updateSelectedDepartment(
- selectedDepartment?.deptId,
- item?.deptId
- )
- }}
- >
-
-
- {item?.dept}
-
- {item?.primary ? : null}
-
- {`${item?.deptAbv} - ${departmentTypeMap[item?.type]}`}
-
-
- );
- })}
+
+ {intersection1}
+
+
+
+
+
+ {intersection2}
+
+
+
+
+
+
+ {selectedDepartment?.supervisor ? (
+
+
+
+
+
+ {`${name}`}
+
+ {
+ return callNumber(callBackNumber);
+ }}
+ >
+
+ {`${callBackNumber}`}
+
+
+
+ {
+ return callNumber(callBackNumber);
+ }}
+ >
+
+
+
+
+
-
-
- );
- }
\ No newline at end of file
+ ) : null}
+
+
+
+ {`${age} `}
+
+
+ {`${gender} - `}
+
+
+ {`Conscious: ${conscious} | `}
+
+
+ {`Breathing: ${breathing}`}
+
+
+
+ {`${statement}`}
+
+
+
+
+
+
+
+
+
+
+ {units?.length > 0 ? (
+
+ {ownDepartmentResponse?.length > 0 ? (
+
+
+
+ Units
+ Disp
+ Resp
+ Arr
+ {selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
+ Trans
+ ) : null}
+ BIS
+
+
+
+ {ownDepartmentResponse?.map((unit) => {
+ return (
+
+
+ {unit?.unit}
+
+
+ {formatResponseTimes(unit?.dispatched)}
+
+
+ {formatResponseTimes(unit?.responding)}
+
+
+ {formatResponseTimes(unit?.onScene)}
+
+ {selectedDepartment?.type === 'EMS' ||
+ selectedDepartment?.type === 'Rescue' ? (
+
+ {formatResponseTimes(unit?.transporting)}
+
+ ) : null}
+
+ {formatResponseTimes(unit?.inService)}
+
+
+ )
+ })}
+
+
+
+ ) : (
+
+
+ {`No ${selectedDepartment?.dept} Units Responding`}
+
+
+ )}
+ {mutualAidDepartmentResponse?.length > 0 ? (
+
+
+
+ M/A
+ Disp
+ Resp
+ Arr
+ {selectedDepartment?.type === 'EMS' || selectedDepartment?.type === 'Rescue' ? (
+ Trans
+ ) : null}
+ BIS
+
+
+ {mutualAidDepartmentResponse?.map((unit) => {
+ return (
+
+
+ {unit?.unit}
+
+
+ {formatResponseTimes(unit?.dispatched)}
+
+
+ {formatResponseTimes(unit?.responding)}
+
+
+ {formatResponseTimes(unit?.onScene)}
+
+ {selectedDepartment?.type === 'EMS' ||
+ selectedDepartment?.type === 'Rescue' ? (
+
+ {formatResponseTimes(unit?.transporting)}
+
+ ) : null}
+
+ {formatResponseTimes(unit?.inService)}
+
+
+ )
+ })}
+
+ ) : null}
+
+ ) : (
+
+ No Units Responding
+
+ )}
+
+
+
+
+
+ Incident Notes
+
+
+
+
+ {notes?.split('\n').map((note, index) => (
+
+ {note}
+ {index < notes.split('\n').length - 1 && (
+
+ )}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {deptList?.map((item) => {
+ return (
+
+ {
+ actionSheetRef.current?.hide();
+ return updateSelectedDepartment(
+ selectedDepartment?.deptId,
+ item?.deptId
+ )
+ }}
+ >
+
+
+ {item?.dept}
+
+ {item?.primary ? : null}
+
+ {`${item?.deptAbv} - ${departmentTypeMap[item?.type]}`}
+
+
+ );
+ })}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/incidents.jsx b/app/incidents.jsx
index ac6737c..a5c0308 100644
--- a/app/incidents.jsx
+++ b/app/incidents.jsx
@@ -15,6 +15,10 @@ 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();
@@ -49,17 +53,8 @@ export default function Incidents() {
return (
-
-
-
{selectedDepartment?.deptAbv}
-
-
-
+ }
+ />
@@ -121,7 +115,7 @@ export default function Incidents() {
router.push({
pathname: '/call',
params: {
- callDetails: btoa(JSON.stringify(callItem))
+ callDetails: toBase64Unicode(JSON.stringify(callItem))
}
})
}}
@@ -254,7 +248,7 @@ export default function Incidents() {
-
+
-
-
-
+
Back to Login
-
-
-
+ }
+ />
,
+ centerHeader = ,
+ rightHeader =
}) => {
return (
- {children}
+
+
+ {leftHeader}
+ {centerHeader}
+ {rightHeader}
+
+
)
}
export const PageFooter = ({
- children
+ children
}) => {
return (
@@ -224,11 +247,11 @@ export const PageFooter = ({
}
export const LoginTextInput = ({
- label,
- icon,
- isPassword = false,
- hidePassword = true,
- setHidePassword = (boolean) => {},
+ label,
+ icon,
+ isPassword = false,
+ hidePassword = true,
+ setHidePassword = (boolean) => { },
...props
}) => {
return (
@@ -239,7 +262,7 @@ export const LoginTextInput = ({
{label}
{isPassword ? (
- {setHidePassword(!hidePassword)}}>
+ { setHidePassword(!hidePassword) }}>
) : null}
@@ -249,115 +272,108 @@ export const LoginTextInput = ({
export const RegisterDropdownInput = ({
label,
- isOpen,
- setOpen,
+ isOpen: _isOpen,
+ setOpen: _setOpen,
selectedValue,
onValueChange,
menu
}) => {
+ const [modalVisible, setModalVisible] = useState(false);
+
return (
{label}
- {
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
- LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity'));
- isOpen ? setOpen(false) : setOpen(true);
+ onPress={() => setModalVisible(true)}
+ >
+
+
+ {selectedValue ? providerConversion[selectedValue] : menu.placeholder}
+
+ setModalVisible(!modalVisible)}>
+
+
+
+ setModalVisible(false)}
+ >
+ setModalVisible(false)}
+ />
+
-
-
-
- {selectedValue ? providerConversion[selectedValue] : menu.placeholder}
-
- {
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
- LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity'));
- isOpen ? setOpen(false) : setOpen(true);
+ setModalVisible(false)}
+ >
+
+ Close
+
+
+ {
+ onValueChange(value);
}}
>
-
-
-
- {isOpen &&
- {menu.dropdownList.map((subMenu, index) => {
- return (
- {
- onValueChange(subMenu.value);
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
- LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity'));
- setOpen(false);
- }}
- >
-
- {subMenu.label}
- {selectedValue === subMenu.value &&
-
-
-
- }
-
-
- )
- })}
- }
-
+ {menu.dropdownList.map((item) => (
+
+ ))}
+
+
+
- )
+ );
}
-export const formatPhoneNumber = (e) => {
+export const formatPhoneNumber = (e) => {
let formattedNumber;
const length = e?.length;
const regex = () => e.replace(/[^0-9\.]+/g, "");
const areaCode = () => `(${regex().slice(0, 3)})`;
const firstSix = () => `${areaCode()} ${regex().slice(3, 6)}`;
- const trailer = (start) => `${regex().slice(start, regex().length)}`;
+ const trailer = (start) => `${regex().slice(start, regex().length)}`;
if (length <= 3) {
formattedNumber = regex();
} else if (length === 4) {
formattedNumber = `${areaCode()} ${trailer(3)}`;
} else if (length === 5) {
- formattedNumber = `${areaCode().replace(")", "")}`;
+ formattedNumber = `${areaCode().replace(")", "")}`;
} else if (length >= 5 && length <= 9) {
- formattedNumber = `${areaCode()} ${trailer(3)}`;
+ formattedNumber = `${areaCode()} ${trailer(3)}`;
} else if (length >= 10) {
formattedNumber = `${firstSix()}-${trailer(6)}`;
- }
+ }
return formattedNumber;
};
\ No newline at end of file
diff --git a/contexts/WebSocketContext.js b/contexts/WebSocketContext.js
index 641a59e..f9354ca 100644
--- a/contexts/WebSocketContext.js
+++ b/contexts/WebSocketContext.js
@@ -20,7 +20,7 @@ export const WebSocketProvider = ({ children }) => {
}
console.log(`🔁 Connecting (Attempt ${reconnectAttempts.current + 1}/${maxReconnectAttempts})`);
- ws.current = new WebSocket(process.env.EXPO_PUBLIC_WS_URL);
+ ws.current = new WebSocket(`${process.env.EXPO_PUBLIC_WS_URL}/callfeed`);
ws.current.onopen = () => {
console.log('✅ WebSocket connected');
diff --git a/hooks/useCallFeed/useCallFeed.jsx b/hooks/useCallFeed/useCallFeed.jsx
index 9437be1..31f413a 100644
--- a/hooks/useCallFeed/useCallFeed.jsx
+++ b/hooks/useCallFeed/useCallFeed.jsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react";
import { useDepartments } from "../useDepartments";
import {
- C,
Cardiology,
Cpr,
FourByFour,
@@ -107,7 +106,7 @@ const getIncidents = async (departments, incidentStatus) => {
return response?.json() || [];
};
-export const useCallFeed = () => {
+export const useCallFeed = (callPage = false) => {
const departments = useDepartments();
const { lastMessage } = useWebSocketContext();
const [allCalls, setAllCalls] = useState([]);
@@ -123,7 +122,7 @@ export const useCallFeed = () => {
setAllCalls(incidents);
setInit(false);
}
- fetchData();
+ if (!callPage) fetchData();
}, []);
useEffect(() => {
@@ -141,7 +140,7 @@ export const useCallFeed = () => {
setAllCalls(incidents);
}
if (!init) {
- fetchData();
+ if (!callPage) fetchData();
}
}, [selectedStatus]);
diff --git a/package-lock.json b/package-lock.json
index 79022e2..5329613 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@emotion/unitless": "^0.10.0",
"@expo/vector-icons": "^14.0.2",
"@react-native-async-storage/async-storage": "^1.24.0",
+ "@react-native-picker/picker": "^2.11.1",
"@react-navigation/native": "^7.1.17",
"expo": "^53.0.20",
"expo-constants": "~17.1.7",
@@ -3114,6 +3115,19 @@
"react-native": "^0.0.0-0 || >=0.60 <1.0"
}
},
+ "node_modules/@react-native-picker/picker": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz",
+ "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==",
+ "license": "MIT",
+ "workspaces": [
+ "example"
+ ],
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz",
diff --git a/package.json b/package.json
index 38e0e53..b3739a8 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@emotion/unitless": "^0.10.0",
"@expo/vector-icons": "^14.0.2",
"@react-native-async-storage/async-storage": "^1.24.0",
+ "@react-native-picker/picker": "^2.11.1",
"@react-navigation/native": "^7.1.17",
"expo": "^53.0.20",
"expo-constants": "~17.1.7",