Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions src/app/call/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { format, isValid } from 'date-fns';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import { ClockIcon, FileTextIcon, ImageIcon, InfoIcon, LoaderIcon, MessageSquareIcon, PaperclipIcon, RouteIcon, TimerIcon, UserIcon, UsersIcon, VideoIcon } from 'lucide-react-native';
import { useColorScheme } from 'nativewind';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
Expand All @@ -12,6 +11,7 @@ import { CheckInTabContent } from '@/components/check-in-timers/check-in-tab-con
import { MessageCommanderSheet } from '@/components/command/message-commander-sheet';
import { ReopenCommandSheet } from '@/components/command/reopen-command-sheet';
import { StartCommandSheet } from '@/components/command/start-command-sheet';
import { HeaderBackButton } from '@/components/common/header-back-button';
import { Loading } from '@/components/common/loading';
import ZeroState from '@/components/common/zero-state';
// Import a static map component instead of react-native-maps
Expand Down Expand Up @@ -84,9 +84,6 @@ export default function CallDetail() {
const stopPolling = useCheckInTimerStore((state) => state.stopPolling);
const resetTimers = useCheckInTimerStore((state) => state.reset);

const { colorScheme } = useColorScheme();
const textColor = colorScheme === 'dark' ? '#FFFFFF' : '#000000';

// Get current user location from the location store
const userLatitude = useLocationStore((state) => state.latitude);
const userLongitude = useLocationStore((state) => state.longitude);
Expand Down Expand Up @@ -288,6 +285,7 @@ export default function CallDetail() {
options={{
title: t('call_detail.title'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={handleBack} />,
headerRight: HeaderRightMenu,
headerBackTitle: '',
}}
Expand All @@ -307,6 +305,7 @@ export default function CallDetail() {
options={{
title: t('call_detail.title'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={handleBack} />,
headerRight: HeaderRightMenu,
headerBackTitle: '',
}}
Expand All @@ -328,6 +327,7 @@ export default function CallDetail() {
options={{
title: t('call_detail.title'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={handleBack} />,
headerBackTitle: '',
}}
/>
Expand All @@ -352,7 +352,7 @@ export default function CallDetail() {
title: t('call_detail.tabs.info'),
icon: <InfoIcon size={16} />,
content: (
<Box className={`p-4 shadow-xs ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
<Box className="p-4">
<VStack className="space-y-3">
<Box className="border-b border-outline-100 pb-2">
<Text className="text-sm text-gray-500">{t('call_detail.priority')}</Text>
Expand Down Expand Up @@ -425,7 +425,7 @@ export default function CallDetail() {
{callExtraData?.Protocols && callExtraData.Protocols.length > 0 ? (
<VStack className="space-y-3">
{callExtraData.Protocols.map((protocol, index) => (
<Box key={index} className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
<Box key={index} className="rounded-lg bg-gray-50 p-3 dark:bg-gray-700">
<Text className="font-semibold">{protocol.Name}</Text>
<Text className="text-sm text-gray-600 dark:text-gray-400">{protocol.Description}</Text>
<Box>
Expand All @@ -449,7 +449,7 @@ export default function CallDetail() {
{callExtraData?.Dispatches && callExtraData.Dispatches.length > 0 ? (
<VStack className="space-y-3">
{callExtraData.Dispatches.map((dispatched, index) => (
<Box key={index} className={`rounded-lg p-3 ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
<Box key={index} className="rounded-lg bg-gray-50 p-3 dark:bg-gray-700">
<Text className="font-semibold">{dispatched.Name}</Text>
<HStack className="mt-1">
<Text className="mr-2 text-sm text-gray-600">
Expand Down Expand Up @@ -527,13 +527,14 @@ export default function CallDetail() {
options={{
title: t('call_detail.title'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={handleBack} />,
headerRight: HeaderRightMenu,
headerBackTitle: '',
}}
/>
<ScrollView className={`size-full w-full flex-1 ${colorScheme === 'dark' ? 'bg-neutral-950' : 'bg-neutral-50'}`}>
<ScrollView className="size-full w-full flex-1 bg-gray-50 dark:bg-gray-900" contentContainerStyle={{ paddingBottom: 16 }}>
{/* Header */}
<Box className={`p-4 shadow-xs ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
<Box className="mx-4 mt-3 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<HStack className="mb-2 items-center justify-between">
<Heading size="md">
{call.Name} ({call.Number})
Expand Down Expand Up @@ -572,13 +573,13 @@ export default function CallDetail() {

{/* Map - only show when valid coordinates exist */}
{coordinates.latitude !== null && coordinates.longitude !== null ? (
<Box className="w-full">
<Box className="mx-4 mt-3 overflow-hidden rounded-xl shadow-xs">
<StaticMap latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} height={200} showUserLocation={true} />
</Box>
) : null}

{/* Action Buttons */}
<HStack className={`justify-around p-4 shadow-xs ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
<HStack className="mx-4 mt-3 justify-around rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<Box className="relative mx-1 flex-1">
<Button onPress={() => openNotesModal()} variant="outline" className="w-full" size={isLandscape ? 'md' : 'sm'}>
<ButtonIcon as={FileTextIcon} />
Expand Down Expand Up @@ -621,7 +622,7 @@ export default function CallDetail() {
</HStack>

{/* Tabs */}
<Box className={`mt-2 flex-1 pb-8 ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
<Box className="mx-4 mb-4 mt-3 flex-1 overflow-hidden rounded-xl bg-white pb-8 shadow-xs dark:bg-gray-800">
<SharedTabs tabs={renderTabs()} variant="underlined" size={isLandscape ? 'md' : 'sm'} />
</Box>
</ScrollView>
Expand Down
24 changes: 14 additions & 10 deletions src/app/call/[id]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as z from 'zod';

import { DestinationPoiSelector } from '@/components/calls/destination-poi-selector';
import { DispatchSelectionModal } from '@/components/calls/dispatch-selection-modal';
import { HeaderBackButton } from '@/components/common/header-back-button';
import { Loading } from '@/components/common/loading';
import FullScreenLocationPicker from '@/components/maps/full-screen-location-picker';
import LocationPicker from '@/components/maps/location-picker';
Expand Down Expand Up @@ -417,6 +418,7 @@ export default function EditCall() {
options={{
title: t('calls.edit_call'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={() => router.back()} />,
headerBackTitle: '',
}}
/>
Expand All @@ -432,6 +434,7 @@ export default function EditCall() {
options={{
title: t('calls.edit_call'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={() => router.back()} />,
headerBackTitle: '',
}}
/>
Expand All @@ -450,15 +453,16 @@ export default function EditCall() {
options={{
title: t('calls.edit_call'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={() => router.back()} />,
headerBackTitle: '',
}}
/>
<View className="size-full flex-1">
<Box className={`size-full w-full flex-1 ${colorScheme === 'dark' ? 'bg-neutral-950' : 'bg-neutral-50'}`}>
<Box className="size-full w-full flex-1 bg-gray-50 dark:bg-gray-900">
<ScrollView className="flex-1 px-4 py-6">
<Text className="mb-6 text-2xl font-bold">{t('calls.edit_call_description')}</Text>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.name}>
<FormControlLabel>
<FormControlLabelText>{t('calls.name')}</FormControlLabelText>
Expand All @@ -480,7 +484,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.nature}>
<FormControlLabel>
<FormControlLabelText>{t('calls.nature')}</FormControlLabelText>
Expand All @@ -502,7 +506,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.priority}>
<FormControlLabel>
<FormControlLabelText>{t('calls.priority')}</FormControlLabelText>
Expand Down Expand Up @@ -535,7 +539,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.type}>
<FormControlLabel>
<FormControlLabelText>{t('calls.type')}</FormControlLabelText>
Expand Down Expand Up @@ -568,7 +572,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.note')}</FormControlLabelText>
Expand All @@ -585,7 +589,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<Text className="mb-4 text-lg font-semibold">{t('calls.call_location')}</Text>

{/* Address Field */}
Expand Down Expand Up @@ -637,7 +641,7 @@ export default function EditCall() {
/>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.contact_name')}</FormControlLabelText>
Expand All @@ -654,7 +658,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.contact_info')}</FormControlLabelText>
Expand All @@ -671,7 +675,7 @@ export default function EditCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<Text className="mb-4 text-lg font-semibold">{t('calls.dispatch_to')}</Text>
<Button onPress={() => setShowDispatchModal(true)} className="w-full">
<ButtonText>{getDispatchSummary()}</ButtonText>
Expand Down
22 changes: 12 additions & 10 deletions src/app/call/new/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as z from 'zod';
import { createCall } from '@/api/calls/calls';
import { DestinationPoiSelector } from '@/components/calls/destination-poi-selector';
import { DispatchSelectionModal } from '@/components/calls/dispatch-selection-modal';
import { HeaderBackButton } from '@/components/common/header-back-button';
import { Loading } from '@/components/common/loading';
import FullScreenLocationPicker from '@/components/maps/full-screen-location-picker';
import LocationPicker from '@/components/maps/location-picker';
Expand Down Expand Up @@ -660,15 +661,16 @@ export default function NewCall() {
options={{
title: t('calls.new_call'),
headerShown: true,
headerLeft: () => <HeaderBackButton onPress={() => router.back()} />,
headerBackTitle: '',
}}
/>
<View className="size-full flex-1">
<Box className={`size-full w-full flex-1 ${colorScheme === 'dark' ? 'bg-neutral-950' : 'bg-neutral-50'}`}>
<Box className="size-full w-full flex-1 bg-gray-50 dark:bg-gray-900">
<ScrollView className="flex-1 px-4 py-6" contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 16) }} style={{ paddingTop: Math.max(insets.top, 16) }}>
<Text className="mb-6 text-2xl font-bold">{t('calls.create_new_call')}</Text>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.name}>
<FormControlLabel>
<FormControlLabelText>{t('calls.name')}</FormControlLabelText>
Expand All @@ -690,7 +692,7 @@ export default function NewCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.nature}>
<FormControlLabel>
<FormControlLabelText>{t('calls.nature')}</FormControlLabelText>
Expand All @@ -712,7 +714,7 @@ export default function NewCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.priority}>
<FormControlLabel>
<FormControlLabelText>{t('calls.priority')}</FormControlLabelText>
Expand Down Expand Up @@ -745,7 +747,7 @@ export default function NewCall() {
</FormControl>
</Card>

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl isInvalid={!!errors.type}>
<FormControlLabel>
<FormControlLabelText>{t('calls.type')}</FormControlLabelText>
Expand Down Expand Up @@ -779,7 +781,7 @@ export default function NewCall() {
</Card>

{fieldPolicy.isVisible(NewCallFieldKeys.Note) ? (
<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.note')}</FormControlLabelText>
Expand All @@ -797,7 +799,7 @@ export default function NewCall() {
</Card>
) : null}

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<Text className="mb-4 text-lg font-semibold">{t('calls.call_location')}</Text>

{/* Address Field */}
Expand Down Expand Up @@ -919,7 +921,7 @@ export default function NewCall() {
</Card>

{fieldPolicy.isVisible(NewCallFieldKeys.ContactName) ? (
<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.contact_name')}</FormControlLabelText>
Expand All @@ -938,7 +940,7 @@ export default function NewCall() {
) : null}

{fieldPolicy.isVisible(NewCallFieldKeys.ContactInfo) ? (
<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<FormControl>
<FormControlLabel>
<FormControlLabelText>{t('calls.contact_info')}</FormControlLabelText>
Expand All @@ -956,7 +958,7 @@ export default function NewCall() {
</Card>
) : null}

<Card className={`mb-8 rounded-lg border p-4 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}>
<Card className="mb-4 rounded-xl bg-white p-4 shadow-xs dark:bg-gray-800">
<Text className="mb-4 text-lg font-semibold">{t('calls.dispatch_to')}</Text>
<Button onPress={() => setShowDispatchModal(true)} className="w-full">
<ButtonText>{getDispatchSummary()}</ButtonText>
Expand Down
17 changes: 17 additions & 0 deletions src/components/calls/__tests__/dispatch-selection-basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ jest.mock('nativewind', () => ({
cssInterop: jest.fn(),
}));

// The gluestack Actionsheet does not render its children in the jest environment,
// so mock it with plain Views the same way the other bottom-sheet tests do.
jest.mock('@/components/ui/actionsheet', () => {
const { View } = require('react-native');
return {
Actionsheet: ({ isOpen, children, testID }: any) => (isOpen ? <View testID={testID ?? 'actionsheet'}>{children}</View> : null),
ActionsheetBackdrop: ({ children }: any) => <View testID="actionsheet-backdrop">{children}</View>,
ActionsheetContent: ({ children, style }: any) => (
<View testID="actionsheet-content" style={style}>
{children}
</View>
),
ActionsheetDragIndicator: () => <View testID="actionsheet-drag-indicator" />,
ActionsheetDragIndicatorWrapper: ({ children }: any) => <View testID="actionsheet-drag-indicator-wrapper">{children}</View>,
};
});

// Mock cssInterop globally
(global as any).cssInterop = jest.fn();

Expand Down
Loading
Loading