Files
vip-coordinator/frontend/src/hooks/useSignalMessages.ts
kyle f2b3f34a72 refactor: code efficiency improvements (Issues #9-13, #15, #17-20)
Backend:
- Extract shared hard-delete authorization utility (#9)
- Extract Prisma include constants per entity (#11)
- Fix N+1 query pattern in events findAll (#12)
- Extract shared date utility functions (#13)
- Move vehicle utilization filtering to DB query (#15)
- Add ParseBooleanPipe for query params
- Add CurrentDriver decorator + ResolveDriverInterceptor (#20)

Frontend:
- Extract shared form utilities (toDatetimeLocal) and enum labels (#17)
- Replace browser confirm() with styled ConfirmModal (#18)
- Add centralized query-keys.ts constants (#19)
- Clean up unused imports, add useMemo where needed (#19)
- Standardize filter button styling across list pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:07:19 +01:00

142 lines
4.0 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../lib/api';
import { queryKeys } from '../lib/query-keys';
export interface SignalMessage {
id: string;
driverId: string;
direction: 'INBOUND' | 'OUTBOUND';
content: string;
timestamp: string;
isRead: boolean;
}
export interface UnreadCounts {
[driverId: string]: number;
}
/**
* Fetch messages for a specific driver
*/
export function useDriverMessages(driverId: string | null, enabled = true) {
return useQuery({
queryKey: driverId ? queryKeys.signal.messages(driverId) : ['signal-messages', null],
queryFn: async () => {
if (!driverId) return [];
const { data } = await api.get<SignalMessage[]>(`/signal/messages/driver/${driverId}`);
return data;
},
enabled: enabled && !!driverId,
refetchInterval: 5000, // Poll for new messages every 5 seconds
});
}
/**
* Fetch unread message counts for all drivers
*/
export function useUnreadCounts() {
return useQuery({
queryKey: queryKeys.signal.unreadCounts,
queryFn: async () => {
const { data } = await api.get<UnreadCounts>('/signal/messages/unread');
return data;
},
refetchInterval: 10000, // Poll every 10 seconds
});
}
/**
* Check which events have driver responses since the event started
* @param events Array of events with id, driverId, and startTime
*/
export function useDriverResponseCheck(
events: Array<{ id: string; driver?: { id: string } | null; startTime: string }>
) {
// Only include events that have a driver
const eventsWithDrivers = events.filter((e) => e.driver?.id);
const eventIds = eventsWithDrivers.map((e) => e.id).join(',');
return useQuery({
queryKey: queryKeys.signal.driverResponses(eventIds),
queryFn: async () => {
if (eventsWithDrivers.length === 0) {
return new Set<string>();
}
const payload = {
events: eventsWithDrivers.map((e) => ({
eventId: e.id,
driverId: e.driver!.id,
startTime: e.startTime,
})),
};
const { data } = await api.post<{ respondedEventIds: string[] }>(
'/signal/messages/check-responses',
payload
);
return new Set(data.respondedEventIds);
},
enabled: eventsWithDrivers.length > 0,
refetchInterval: 10000, // Poll every 10 seconds
});
}
/**
* Send a message to a driver
*/
export function useSendMessage() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ driverId, content }: { driverId: string; content: string }) => {
const { data } = await api.post<SignalMessage>('/signal/messages/send', {
driverId,
content,
});
return data;
},
onSuccess: (data, variables) => {
// Add the new message to the cache immediately
queryClient.setQueryData<SignalMessage[]>(
queryKeys.signal.messages(variables.driverId),
(old) => [...(old || []), data]
);
// Also invalidate to ensure consistency
queryClient.invalidateQueries({ queryKey: queryKeys.signal.messages(variables.driverId) });
},
});
}
/**
* Mark messages as read for a driver
*/
export function useMarkMessagesAsRead() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (driverId: string) => {
const { data } = await api.post(`/signal/messages/driver/${driverId}/read`);
return data;
},
onSuccess: (_, driverId) => {
// Update the unread counts cache
queryClient.setQueryData<UnreadCounts>(
queryKeys.signal.unreadCounts,
(old) => {
if (!old) return {};
const updated = { ...old };
delete updated[driverId];
return updated;
}
);
// Mark messages as read in the messages cache
queryClient.setQueryData<SignalMessage[]>(
queryKeys.signal.messages(driverId),
(old) => old?.map((msg) => ({ ...msg, isRead: true })) || []
);
},
});
}