fix: tab stack reset, GPS optional for install, status picker mutation fix
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
Reference in New Issue
Block a user