- Client detail: Subscription/Invoices/Payments tabs now fully functional - Payments: proper list with today's total + live search prefill from client - Record payment: debounced live search, reference required for non-cash - Remittances: detail screen with included payments breakdown - Tickets: status filter chips + create button, new ticket with categories - Installations: tab now visible with list + confirm flow - Fix: remove duplicate @react-navigation/elements causing Metro asset error - Fix: metro.config.js asset resolution from node_modules
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { router } from 'expo-router';
|
|
import { api } from '../../../services/api';
|
|
|
|
// Installations are tickets of type INSTALLATION (or filtered by subject prefix)
|
|
// We query tickets with type=INSTALLATION if the API supports it, fallback to all open tickets
|
|
async function fetchInstallations() {
|
|
try {
|
|
const res = await api.get('/api/v1/tickets?type=INSTALLATION&limit=50');
|
|
return res.data?.data ?? res.data ?? [];
|
|
} catch {
|
|
// Fallback: all OPEN tickets
|
|
const res = await api.get('/api/v1/tickets?status=OPEN&limit=50');
|
|
return res.data?.data ?? res.data ?? [];
|
|
}
|
|
}
|
|
|
|
const STATUS_STYLE: Record<string, { bg: string; text: string }> = {
|
|
OPEN: { bg: '#EFF6FF', text: '#2563EB' },
|
|
IN_PROGRESS: { bg: '#FFFBEB', text: '#D97706' },
|
|
RESOLVED: { bg: '#F0FDF4', text: '#16A34A' },
|
|
CLOSED: { bg: '#F3F4F6', text: '#6B7280' },
|
|
};
|
|
|
|
export default function InstallationsScreen() {
|
|
const { data, isLoading, refetch, isRefetching } = useQuery({
|
|
queryKey: ['installations'],
|
|
queryFn: fetchInstallations,
|
|
});
|
|
|
|
const installations: any[] = data ?? [];
|
|
|
|
return (
|
|
<View className="flex-1 bg-gray-50">
|
|
{/* Header */}
|
|
<View className="px-4 pt-14 pb-4 bg-blue-600">
|
|
<Text className="text-white text-xl font-bold">Installations</Text>
|
|
<Text className="text-white/70 text-xs mt-0.5">
|
|
{installations.length} pending
|
|
</Text>
|
|
</View>
|
|
|
|
{isLoading ? (
|
|
<View className="flex-1 items-center justify-center">
|
|
<ActivityIndicator color="#2563EB" />
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={installations}
|
|
keyExtractor={(item) => item.id}
|
|
refreshControl={<RefreshControl refreshing={isRefetching} onRefresh={refetch} />}
|
|
contentContainerStyle={{ padding: 16 }}
|
|
renderItem={({ item }) => {
|
|
const statusStyle = STATUS_STYLE[item.status] ?? { bg: '#F3F4F6', text: '#6B7280' };
|
|
return (
|
|
<TouchableOpacity
|
|
className="bg-white rounded-xl p-4 mb-3 border border-gray-100"
|
|
onPress={() => router.push(`/(app)/installations/${item.id}`)}
|
|
>
|
|
<View className="flex-row justify-between items-start mb-2">
|
|
<Text className="font-semibold text-gray-900 flex-1 mr-2" numberOfLines={2}>
|
|
{item.subject}
|
|
</Text>
|
|
<View className="rounded-full px-2.5 py-1" style={{ backgroundColor: statusStyle.bg }}>
|
|
<Text className="text-xs font-semibold" style={{ color: statusStyle.text }}>
|
|
{item.status?.replace('_', ' ')}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="flex-row items-center">
|
|
<Text className="text-gray-500 text-sm flex-1">
|
|
{item.client?.firstName} {item.client?.lastName}
|
|
</Text>
|
|
<Text className="text-gray-400 text-xs">
|
|
{item.createdAt ? new Date(item.createdAt).toLocaleDateString() : ''}
|
|
</Text>
|
|
</View>
|
|
|
|
{item.client?.address && (
|
|
<Text className="text-gray-400 text-xs mt-1 ml-0" numberOfLines={1}>
|
|
📍 {item.client.address}
|
|
</Text>
|
|
)}
|
|
|
|
{/* Confirm button if not yet resolved */}
|
|
{item.status !== 'RESOLVED' && item.status !== 'CLOSED' && (
|
|
<TouchableOpacity
|
|
className="mt-3 bg-green-600 rounded-xl py-2.5 items-center"
|
|
onPress={() => router.push(`/(app)/installations/${item.id}`)}
|
|
>
|
|
<Text className="text-white font-semibold text-sm">📷 Confirm Installation</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
}}
|
|
ListEmptyComponent={
|
|
<View className="items-center py-16">
|
|
<Text className="text-5xl mb-4">🔌</Text>
|
|
<Text className="text-gray-700 font-semibold text-base">No installations pending</Text>
|
|
<Text className="text-gray-400 text-sm mt-1">All caught up!</Text>
|
|
</View>
|
|
}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|