feat: complete Sprint 3 Expo mobile app scaffold + all screens
This commit is contained in:
83
app/(app)/installations/[id].tsx
Normal file
83
app/(app)/installations/[id].tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, Alert, Image, ActivityIndicator } from 'react-native';
|
||||
import { useLocalSearchParams, router } from 'expo-router';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as Location from 'expo-location';
|
||||
import { api } from '../../../services/api';
|
||||
|
||||
export default function InstallationConfirmScreen() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [photo, setPhoto] = useState<string | null>(null);
|
||||
const [coords, setCoords] = useState<{ lat: number; lng: number } | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [gpsLoading, setGpsLoading] = useState(false);
|
||||
|
||||
const capturePhoto = async () => {
|
||||
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (status !== 'granted') return Alert.alert('Permission denied', 'Camera access is required.');
|
||||
const result = await ImagePicker.launchCameraAsync({ quality: 0.7, base64: false });
|
||||
if (!result.canceled) setPhoto(result.assets[0].uri);
|
||||
};
|
||||
|
||||
const captureGPS = async () => {
|
||||
setGpsLoading(true);
|
||||
try {
|
||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||
if (status !== 'granted') { Alert.alert('Permission denied', 'Location access is required.'); return; }
|
||||
const loc = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High });
|
||||
setCoords({ lat: loc.coords.latitude, lng: loc.coords.longitude });
|
||||
} catch { Alert.alert('Error', 'Could not get location.'); }
|
||||
finally { setGpsLoading(false); }
|
||||
};
|
||||
|
||||
const confirm = async () => {
|
||||
if (!coords) return Alert.alert('Required', 'Capture GPS location first.');
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.patch(`/api/v1/tickets/${id}/confirm-installation`, {
|
||||
latitude: coords.lat,
|
||||
longitude: coords.lng,
|
||||
photoUrl: photo,
|
||||
});
|
||||
Alert.alert('Done!', 'Installation confirmed.', [{ text: 'OK', onPress: () => router.back() }]);
|
||||
} catch (e: any) {
|
||||
Alert.alert('Error', e?.response?.data?.message ?? 'Confirmation failed.');
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<View className="px-4 pt-14 pb-4 bg-primary flex-row items-center">
|
||||
<TouchableOpacity onPress={() => router.back()} className="mr-3">
|
||||
<Text className="text-white text-lg">←</Text>
|
||||
</TouchableOpacity>
|
||||
<Text className="text-white text-xl font-bold">Installation Confirmation</Text>
|
||||
</View>
|
||||
<ScrollView className="flex-1 px-4 py-6">
|
||||
<View className="bg-white rounded-2xl border border-gray-100 p-4 mb-4">
|
||||
<Text className="font-semibold text-gray-700 mb-3">📍 GPS Location</Text>
|
||||
{coords ? (
|
||||
<Text className="text-green-700 font-medium">✓ {coords.lat.toFixed(6)}, {coords.lng.toFixed(6)}</Text>
|
||||
) : (
|
||||
<Text className="text-gray-400 mb-3">No location captured yet</Text>
|
||||
)}
|
||||
<TouchableOpacity className="bg-primary rounded-xl py-3 items-center mt-3" onPress={captureGPS} disabled={gpsLoading}>
|
||||
{gpsLoading ? <ActivityIndicator color="white" /> : <Text className="text-white font-semibold">Capture GPS</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View className="bg-white rounded-2xl border border-gray-100 p-4 mb-8">
|
||||
<Text className="font-semibold text-gray-700 mb-3">📷 Photo Proof</Text>
|
||||
{photo && <Image source={{ uri: photo }} className="w-full h-48 rounded-xl mb-3" resizeMode="cover" />}
|
||||
<TouchableOpacity className="border border-primary rounded-xl py-3 items-center" onPress={capturePhoto}>
|
||||
<Text className="text-primary font-semibold">{photo ? 'Retake Photo' : 'Take Photo'}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity className="bg-green-600 rounded-xl py-4 items-center" onPress={confirm} disabled={loading}>
|
||||
{loading ? <ActivityIndicator color="white" /> : <Text className="text-white font-bold text-base">✓ Confirm Installation</Text>}
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user