84 lines
4.1 KiB
TypeScript
84 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
}
|