Feature/callpage #18
8 changed files with 1642 additions and 3518 deletions
|
|
@ -94,7 +94,7 @@ export default function Incidents() {
|
||||||
{sortedAndFilteredCalls?.length ? (
|
{sortedAndFilteredCalls?.length ? (
|
||||||
sortedAndFilteredCalls?.map((callItem, index) => {
|
sortedAndFilteredCalls?.map((callItem, index) => {
|
||||||
const { data: call, timestamp } = callItem;
|
const { data: call, timestamp } = callItem;
|
||||||
const { Incident, Response } = call;
|
const { Incident, Address, Response } = call;
|
||||||
const {
|
const {
|
||||||
ServiceNumber,
|
ServiceNumber,
|
||||||
IncDate,
|
IncDate,
|
||||||
|
|
@ -103,13 +103,20 @@ export default function Incidents() {
|
||||||
IncNatureCodeDesc,
|
IncNatureCodeDesc,
|
||||||
Status,
|
Status,
|
||||||
} = Incident;
|
} = Incident;
|
||||||
|
const {
|
||||||
|
StreetAddress,
|
||||||
|
AddressApartment,
|
||||||
|
Town,
|
||||||
|
State,
|
||||||
|
LocationName,
|
||||||
|
} = Address;
|
||||||
const {
|
const {
|
||||||
ServiceName
|
ServiceName
|
||||||
} = Response;
|
} = Response;
|
||||||
const SelectedIcon = callIconMap[IncNature] || AccidentAndEmergency;
|
const SelectedIcon = callIconMap[IncNature] || AccidentAndEmergency;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={`callDetails${index}`}
|
key={`callDetails - ${timestamp}`}
|
||||||
style={{ padding: 2 }}
|
style={{ padding: 2 }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -147,7 +154,7 @@ export default function Incidents() {
|
||||||
<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'
|
||||||
|
|
@ -173,6 +180,49 @@ export default function Incidents() {
|
||||||
height={56}
|
height={56}
|
||||||
/>
|
/>
|
||||||
<View style={{ flexDirection: 'column' }}>
|
<View style={{ flexDirection: 'column' }}>
|
||||||
|
{LocationName ? (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: 'black',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 16
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`${LocationName}`}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View style={{ flexDirection: 'row' }}>
|
||||||
|
<Text
|
||||||
|
style={[{
|
||||||
|
color: 'black',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
{`${StreetAddress}`}
|
||||||
|
</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
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: 'black',
|
color: 'black',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } 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 } from 'expo-router';
|
import { useLocalSearchParams, router } 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';
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,8 @@ import {
|
||||||
TextLinkContent,
|
TextLinkContent,
|
||||||
LoginTextInput
|
LoginTextInput
|
||||||
} from '../components/generalHelpers.jsx';
|
} from '../components/generalHelpers.jsx';
|
||||||
import { useWebSocketContext } from '../hooks/useWebSocketContext';
|
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const { isConnected, lastMessage, sendMessage } = useWebSocketContext();
|
|
||||||
|
|
||||||
const [hidePassword, setHidePassword] = useState(true);
|
const [hidePassword, setHidePassword] = useState(true);
|
||||||
const [loginButtonDisabled, setLoginButtonDisabled] = useState(true);
|
const [loginButtonDisabled, setLoginButtonDisabled] = useState(true);
|
||||||
const [auth, setAuth] = useState(false);
|
const [auth, setAuth] = useState(false);
|
||||||
|
|
@ -126,7 +123,6 @@ export default function Login() {
|
||||||
</Link>
|
</Link>
|
||||||
</View>
|
</View>
|
||||||
<Line />
|
<Line />
|
||||||
<Text>Socket connected: {isConnected ? '✅ Yes' : '❌ No'}</Text>
|
|
||||||
</InnerContainer>
|
</InnerContainer>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ export default function Register() {
|
||||||
<View>
|
<View>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<View style={{ flexDirection: 'row', height: 80, alignItems: 'flex-end' }}>
|
<View style={{ flexDirection: 'row', height: 80, alignItems: 'flex-end' }}>
|
||||||
<TouchableOpacity onPress={router.back} style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<TouchableOpacity onPress={router.back} style={{ flexDirection: 'row', alignItems: 'center', paddingBottom: 5 }}>
|
||||||
<Ionicons name="arrow-back-outline" size={30} color="red" style={{ paddingLeft: 20 }} />
|
<Ionicons name="chevron-back-outline" size={22} color="red" style={{ paddingLeft: 20 }} />
|
||||||
<Text style={{ color: 'red', fontWeight: 600 }}>Back to Login</Text>
|
<Text style={{ color: 'red', fontWeight: 600 }}>Back to Login</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,5 @@ module.exports = function (api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
return {
|
return {
|
||||||
presets: ['babel-preset-expo'],
|
presets: ['babel-preset-expo'],
|
||||||
plugins: [
|
|
||||||
['module:react-native-dotenv', {
|
|
||||||
moduleName: '@env',
|
|
||||||
path: '.env',
|
|
||||||
safe: false,
|
|
||||||
allowUndefined: true
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,32 @@ export const WebSocketContext = createContext(null);
|
||||||
|
|
||||||
export const WebSocketProvider = ({ children }) => {
|
export const WebSocketProvider = ({ children }) => {
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
|
const reconnectInterval = useRef(null);
|
||||||
|
const reconnectAttempts = useRef(0);
|
||||||
|
const maxReconnectAttempts = 5;
|
||||||
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [lastMessage, setLastMessage] = useState(null);
|
const [lastMessage, setLastMessage] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const connect = () => {
|
||||||
ws.current = new WebSocket('wss://your-websocket-server.com');
|
if (reconnectAttempts.current >= maxReconnectAttempts) {
|
||||||
|
console.warn('❌ Max reconnect attempts reached. Giving up.');
|
||||||
|
clearInterval(reconnectInterval.current);
|
||||||
|
ws.current?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔁 Connecting (Attempt ${reconnectAttempts.current + 1}/${maxReconnectAttempts})`);
|
||||||
|
ws.current = new WebSocket(process.env.EXPO_PUBLIC_WS_URL);
|
||||||
|
|
||||||
ws.current.onopen = () => {
|
ws.current.onopen = () => {
|
||||||
console.log('✅ WebSocket connected');
|
console.log('✅ WebSocket connected');
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
|
reconnectAttempts.current = 0;
|
||||||
|
if (reconnectInterval.current) {
|
||||||
|
clearInterval(reconnectInterval.current);
|
||||||
|
reconnectInterval.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.current.onmessage = (e) => {
|
ws.current.onmessage = (e) => {
|
||||||
|
|
@ -27,9 +44,20 @@ export const WebSocketProvider = ({ children }) => {
|
||||||
ws.current.onclose = (e) => {
|
ws.current.onclose = (e) => {
|
||||||
console.log('🔌 WebSocket closed:', e.code, e.reason);
|
console.log('🔌 WebSocket closed:', e.code, e.reason);
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
|
if (!reconnectInterval.current && reconnectAttempts.current < maxReconnectAttempts) {
|
||||||
|
reconnectInterval.current = setInterval(() => {
|
||||||
|
reconnectAttempts.current += 1;
|
||||||
|
connect();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
clearInterval(reconnectInterval.current);
|
||||||
ws.current?.close();
|
ws.current?.close();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
5049
package-lock.json
generated
5049
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "tones",
|
"name": "tones",
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
|
|
@ -32,7 +32,6 @@
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-native": "0.76.9",
|
"react-native": "0.76.9",
|
||||||
"react-native-actions-sheet": "^0.9.7",
|
"react-native-actions-sheet": "^0.9.7",
|
||||||
"react-native-dotenv": "^3.4.11",
|
|
||||||
"react-native-dropdown-picker": "^5.4.6",
|
"react-native-dropdown-picker": "^5.4.6",
|
||||||
"react-native-gesture-handler": "~2.20.2",
|
"react-native-gesture-handler": "~2.20.2",
|
||||||
"react-native-reanimated": "~3.16.1",
|
"react-native-reanimated": "~3.16.1",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue