fix: tab stack reset, GPS optional for install, status picker mutation fix

This commit is contained in:
Nemo
2026-03-24 16:09:21 +08:00
parent e0da23fa56
commit a5cac16924
2 changed files with 49 additions and 35 deletions

View File

@@ -29,9 +29,12 @@ export default function AppLayout() {
/> />
<Tabs.Screen <Tabs.Screen
name="clients" name="clients"
options={{ title: 'Clients', tabBarIcon: ({ color }) => <Icon name="users" size={22} color={color} /> }} options={{ title: 'Clients', tabBarIcon: ({ color }) => <Icon name="users" size={22} color={color} />, unmountOnBlur: true }}
listeners={({ navigation }: any) => ({ listeners={({ navigation }: any) => ({
tabPress: () => { navigation.navigate('clients', { screen: 'index' }); }, tabPress: (e) => {
e.preventDefault();
navigation.reset({ index: 0, routes: [{ name: 'clients' }] });
},
})} })}
/> />
<Tabs.Screen <Tabs.Screen
@@ -40,9 +43,12 @@ export default function AppLayout() {
/> />
<Tabs.Screen <Tabs.Screen
name="tasks" name="tasks"
options={{ title: 'Tickets', tabBarIcon: ({ color }) => <Icon name="ticket" size={22} color={color} /> }} options={{ title: 'Tickets', tabBarIcon: ({ color }) => <Icon name="ticket" size={22} color={color} />, unmountOnBlur: true }}
listeners={({ navigation }: any) => ({ listeners={({ navigation }: any) => ({
tabPress: () => { navigation.navigate('tasks', { screen: 'index' }); }, tabPress: (e) => {
e.preventDefault();
navigation.reset({ index: 0, routes: [{ name: 'tasks' }] });
},
})} })}
/> />
<Tabs.Screen <Tabs.Screen

View File

@@ -84,14 +84,11 @@ export default function TicketDetailScreen() {
}); });
const updateStatus = useMutation({ const updateStatus = useMutation({
mutationFn: async (status: TaskStatus) => { mutationFn: async ({ status, activationTicket, subId }: { status: string; activationTicket: boolean; subId?: string }) => {
await api.patch(`/api/v1/tickets/${id}`, { status }); await api.patch(`/api/v1/tickets/${id}`, { status });
// If this is an activation ticket being RESOLVED → activate subscription // If this is an activation ticket being RESOLVED → activate subscription
if ((status === 'RESOLVED' || status === 'CLOSED') && isActivationTicket) { if ((status === 'RESOLVED' || status === 'CLOSED') && activationTicket && subId) {
const sub = clientDetail?.subscriptions?.[0]; await api.patch(`/api/v1/subscriptions/${subId}`, { status: 'ACTIVE' }).catch(() => {});
if (sub?.id && sub?.status !== 'ACTIVE') {
await api.patch(`/api/v1/subscriptions/${sub.id}`, { status: 'ACTIVE' }).catch(() => {});
}
} }
const who = user?.firstName ?? 'Staff'; const who = user?.firstName ?? 'Staff';
await api.post(`/api/v1/tickets/${id}/messages`, { await api.post(`/api/v1/tickets/${id}/messages`, {
@@ -107,7 +104,10 @@ export default function TicketDetailScreen() {
await refetchClient().catch(() => {}); await refetchClient().catch(() => {});
await refetchInvoices().catch(() => {}); await refetchInvoices().catch(() => {});
}, },
onError: () => Alert.alert('Error', 'Could not update status.'), onError: (e: any) => {
const msg = e?.response?.data?.message ?? 'Could not update status.';
Alert.alert('Error', Array.isArray(msg) ? msg.join('\n') : msg);
},
}); });
const captureLocation = async () => { const captureLocation = async () => {
@@ -128,20 +128,13 @@ export default function TicketDetailScreen() {
}; };
const confirmInstallation = async () => { const confirmInstallation = async () => {
if (!coords) {
Alert.alert('Location Required', 'Please capture the installation coordinates before confirming.', [
{ text: 'Cancel', style: 'cancel' },
{ text: 'Capture Now', onPress: captureLocation },
]);
return;
}
setInstConfirming(true); setInstConfirming(true);
try { try {
// 1. Resolve the ticket // 1. Resolve the ticket
await api.patch(`/api/v1/tickets/${id}`, { status: 'RESOLVED' }); await api.patch(`/api/v1/tickets/${id}`, { status: 'RESOLVED' });
// 2. Update client location with recorded coordinates // 2. Update client location with recorded coordinates (skip if GPS unavailable)
if (ticket?.clientId) { if (ticket?.clientId && coords) {
await api.patch(`/api/v1/clients/${ticket.clientId}`, { await api.patch(`/api/v1/clients/${ticket.clientId}`, {
lat: coords.lat, lat: coords.lat,
lng: coords.lng, lng: coords.lng,
@@ -149,7 +142,7 @@ export default function TicketDetailScreen() {
} }
// 3. Log activity comment // 3. Log activity comment
const coordStr = `${coords.lat.toFixed(6)}, ${coords.lng.toFixed(6)}`; const coordStr = coords ? `${coords.lat.toFixed(6)}, ${coords.lng.toFixed(6)}` : 'Not captured';
const note = instNotes.trim() const note = instNotes.trim()
? `Installation confirmed. Location recorded: ${coordStr}. Notes: ${instNotes.trim()}` ? `Installation confirmed. Location recorded: ${coordStr}. Notes: ${instNotes.trim()}`
: `Installation confirmed. Location recorded: ${coordStr}`; : `Installation confirmed. Location recorded: ${coordStr}`;
@@ -468,26 +461,37 @@ export default function TicketDetailScreen() {
<TouchableOpacity <TouchableOpacity
style={{ style={{
backgroundColor: coords ? '#059669' : '#94A3B8', backgroundColor: '#059669',
borderRadius: 14, paddingVertical: 16, alignItems: 'center', borderRadius: 14, paddingVertical: 16, alignItems: 'center',
}} }}
onPress={() => onPress={() => {
Alert.alert( if (!coords) {
'Confirm Installation', Alert.alert(
`Mark this installation as complete?\n\nLocation: ${coords ? `${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)}` : 'Not captured'}\n\nThis will update the client's location and resolve the ticket.`, 'Location Not Captured',
[ 'Location not captured are you sure you want to proceed without GPS coordinates?',
{ text: 'Cancel', style: 'cancel' }, [
{ text: 'Confirm', onPress: confirmInstallation }, { text: 'Cancel', style: 'cancel' },
] { text: 'Confirm Without Location', onPress: confirmInstallation },
) ]
} );
disabled={instConfirming || !coords} } else {
Alert.alert(
'Confirm Installation',
`Mark this installation as complete?\n\nLocation: ${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)}\n\nThis will update the client's location and resolve the ticket.`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Confirm', onPress: confirmInstallation },
]
);
}
}}
disabled={instConfirming}
activeOpacity={0.8} activeOpacity={0.8}
> >
{instConfirming {instConfirming
? <ActivityIndicator color="#FFF" /> ? <ActivityIndicator color="#FFF" />
: <Text style={{ color: '#FFF', fontSize: 17, fontWeight: '700' }}> : <Text style={{ color: '#FFF', fontSize: 17, fontWeight: '700' }}>
{coords ? '✓ Mark Installation Complete' : 'Capture Location First'} ✓ Mark Installation Complete
</Text> </Text>
} }
</TouchableOpacity> </TouchableOpacity>
@@ -643,7 +647,11 @@ export default function TicketDetailScreen() {
); );
return; return;
} }
updateStatus.mutate(s); updateStatus.mutate({
status: s,
activationTicket: isActivationTicket,
subId: clientDetail?.subscriptions?.[0]?.id,
});
}} }}
disabled={isActive || updateStatus.isPending} disabled={isActive || updateStatus.isPending}
style={{ style={{