From f346951052fe8a211fbb362326b5399a16f31cf0 Mon Sep 17 00:00:00 2001 From: Nemo Date: Tue, 24 Mar 2026 11:57:24 +0800 Subject: [PATCH] fix: tickets freeze (useFocusEffect), comment body render, client status from subscription, activation ticket on install, keyboard avoid payment modal --- app/(app)/clients/[id].tsx | 4 +++- app/(app)/clients/index.tsx | 11 ++++++++++- app/(app)/payments/index.tsx | 4 +++- app/(app)/tasks/[id].tsx | 18 ++++++++++++++---- app/(app)/tasks/index.tsx | 5 +++++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/(app)/clients/[id].tsx b/app/(app)/clients/[id].tsx index 4c38346..ed649ac 100644 --- a/app/(app)/clients/[id].tsx +++ b/app/(app)/clients/[id].tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Linking, Alert, TextInput, Modal } from 'react-native'; +import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Linking, Alert, TextInput, Modal, KeyboardAvoidingView, Platform } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useLocalSearchParams, router } from 'expo-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; @@ -67,6 +67,7 @@ function PayInvoiceModal({ invoice, onClose, onSuccess }: { invoice: any; onClos return ( + Record Payment @@ -115,6 +116,7 @@ function PayInvoiceModal({ invoice, onClose, onSuccess }: { invoice: any; onClos + ); } diff --git a/app/(app)/clients/index.tsx b/app/(app)/clients/index.tsx index 641124a..71dfb31 100644 --- a/app/(app)/clients/index.tsx +++ b/app/(app)/clients/index.tsx @@ -10,8 +10,16 @@ const STATUS_CONFIG: Record} contentContainerStyle={{ padding: 16, paddingBottom: 32 }} renderItem={({ item }) => { - const st = STATUS_CONFIG[item.status] ?? { label: item.status, color: '#475569', bg: '#F1F5F9', accent: '#94A3B8' }; + const statusKey = getClientStatus(item); + const st = STATUS_CONFIG[statusKey] ?? { label: statusKey, color: '#475569', bg: '#F1F5F9', accent: '#94A3B8' }; const plan = item.subscriptions?.[0]?.plan?.name; return ( + Record Payment @@ -82,6 +83,7 @@ function PayInvoiceModal({ invoice, onClose, onSuccess }: { invoice: any; onClos + ); } diff --git a/app/(app)/tasks/[id].tsx b/app/(app)/tasks/[id].tsx index f65e25a..b64e4b9 100644 --- a/app/(app)/tasks/[id].tsx +++ b/app/(app)/tasks/[id].tsx @@ -125,12 +125,22 @@ export default function TicketDetailScreen() { : `Installation confirmed. Location recorded: ${coordStr}`; await api.post(`/api/v1/tickets/${id}/messages`, { body: note }).catch(() => {}); + // 4. Create follow-up activation ticket + await api.post('/api/v1/tickets', { + clientId: ticket?.clientId, + subject: `Account Activation — ${ticket?.client?.firstName ?? ''} ${ticket?.client?.lastName ?? ''}`.trim(), + type: 'BILLING', + priority: 'NORMAL', + description: `Follow-up after installation confirmed. Please activate the client account and generate the first invoice.\n\nInstallation ref: ${id}\nLocation: ${coordStr}`, + }).catch(() => {}); + setInstNotes(''); setCoords(null); - Alert.alert('Installation Complete!', 'Ticket resolved and client location updated.'); + Alert.alert('Installation Complete! ✓', 'Ticket resolved, location updated, and activation ticket created.'); refetch(); qc.invalidateQueries({ queryKey: ['tasks'] }); qc.invalidateQueries({ queryKey: ['client', ticket?.clientId] }); + qc.invalidateQueries({ queryKey: ['client-tickets', ticket?.clientId] }); setActiveTab('comments'); } catch { Alert.alert('Error', 'Could not confirm installation. Please try again.'); @@ -404,7 +414,7 @@ export default function TicketDetailScreen() { ) : ( messages.map((m: any, i: number) => { - const isSystem = m.senderType === 'SYSTEM' || m.message?.startsWith('Status changed') || m.message?.startsWith('Installation confirmed'); + const isSystem = m.senderType === 'SYSTEM' || m.body?.startsWith('Status changed') || m.body?.startsWith('Installation confirmed'); const isMe = m.sender?.id === user?.id; if (isSystem) { @@ -412,7 +422,7 @@ export default function TicketDetailScreen() { return ( - {m.message} + {m.body} {m.createdAt && ( @@ -441,7 +451,7 @@ export default function TicketDetailScreen() { borderWidth: isMe ? 0 : 1, borderColor: '#F1F5F9', }}> - {m.message} + {m.body} {m.createdAt && ( diff --git a/app/(app)/tasks/index.tsx b/app/(app)/tasks/index.tsx index 7c2f478..7dd37d4 100644 --- a/app/(app)/tasks/index.tsx +++ b/app/(app)/tasks/index.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; import { View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, RefreshControl, ScrollView } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useQuery } from '@tanstack/react-query'; +import { useFocusEffect } from 'expo-router'; +import { useCallback } from 'react'; import { router } from 'expo-router'; import { api } from '../../../services/api'; @@ -23,6 +25,9 @@ export default function TasksScreen() { queryFn: () => api.get('/api/v1/tickets?limit=100').then(r => r.data?.data ?? r.data ?? []), }); + // Refetch list every time this screen comes into focus (fixes stale list after navigating back) + useFocusEffect(useCallback(() => { refetch(); }, [])); + const tasks = (data ?? []).filter((t: any) => { const matchSearch = `${t.subject} ${t.client?.firstName ?? ''} ${t.client?.lastName ?? ''}`.toLowerCase().includes(search.toLowerCase()); const matchStatus = statusFilter === 'All' || t.status === statusFilter;