riseup-squad18/src/hooks/useAvailability.ts

279 lines
8.3 KiB
TypeScript

/**
* React Query Hooks - Availability
* Hooks para gerenciamento de disponibilidade com cache inteligente
* @version 1.0
*/
import {
useQuery,
useMutation,
useQueryClient,
UseQueryOptions,
} from "@tanstack/react-query";
import { availabilityService } from "../services";
import type {
DoctorAvailability,
CreateAvailabilityInput,
UpdateAvailabilityInput,
} from "../services/availability/types";
import toast from "react-hot-toast";
// ============================================================================
// QUERY KEYS
// ============================================================================
export const availabilityKeys = {
all: ["availability"] as const,
lists: () => [...availabilityKeys.all, "list"] as const,
list: (doctorId?: string) => [...availabilityKeys.lists(), doctorId] as const,
details: () => [...availabilityKeys.all, "detail"] as const,
detail: (id: string) => [...availabilityKeys.details(), id] as const,
slots: (doctorId: string, date: string) =>
[...availabilityKeys.all, "slots", doctorId, date] as const,
};
// ============================================================================
// QUERY HOOKS
// ============================================================================
/**
* Hook para buscar disponibilidade de um médico
*/
export function useAvailability(
doctorId: string | undefined,
options?: Omit<UseQueryOptions<DoctorAvailability[]>, "queryKey" | "queryFn">
) {
return useQuery({
queryKey: availabilityKeys.list(doctorId),
queryFn: async () => {
if (!doctorId) throw new Error("Doctor ID é obrigatório");
return await availabilityService.list({ doctor_id: doctorId });
},
enabled: !!doctorId,
...options,
});
}
/**
* Hook para buscar uma disponibilidade específica
*/
export function useAvailabilityById(id: string | undefined) {
return useQuery({
queryKey: availabilityKeys.detail(id!),
queryFn: async () => {
if (!id) throw new Error("ID é obrigatório");
const items = await availabilityService.list();
const found = items.find((item) => item.id === id);
if (!found) throw new Error("Disponibilidade não encontrada");
return found;
},
enabled: !!id,
});
}
/**
* Hook para buscar slots disponíveis de um médico em uma data
*/
export function useAvailableSlots(
doctorId: string | undefined,
date: string | undefined,
options?: Omit<UseQueryOptions<string[]>, "queryKey" | "queryFn">
) {
return useQuery({
queryKey: availabilityKeys.slots(doctorId!, date!),
queryFn: async () => {
if (!doctorId || !date)
throw new Error("Doctor ID e Data são obrigatórios");
// Buscar disponibilidade do médico
const availabilities = await availabilityService.list({
doctor_id: doctorId,
});
// Buscar consultas do dia
const { appointmentService } = await import("../services");
const appointments = await appointmentService.list({
doctor_id: doctorId,
scheduled_at: `gte.${date}T00:00:00,lt.${date}T23:59:59`,
});
// Calcular slots livres (simplificado - usar lógica completa do AvailableSlotsPicker)
const occupiedSlots = new Set(
appointments.map((a) => a.scheduled_at.substring(11, 16))
);
const dayOfWeek = new Date(date).getDay();
const dayAvailability = availabilities.filter(
(av) => av.weekday === dayOfWeek
);
const freeSlots: string[] = [];
dayAvailability.forEach((av) => {
const start = parseInt(av.start_time.replace(":", ""));
const end = parseInt(av.end_time.replace(":", ""));
const slotMinutes = av.slot_minutes || 30;
const increment = (slotMinutes / 60) * 100;
for (let time = start; time < end; time += increment) {
const hour = Math.floor(time / 100);
const minute = time % 100;
const timeStr = `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
if (!occupiedSlots.has(timeStr)) {
freeSlots.push(timeStr);
}
}
});
return freeSlots.sort();
},
enabled: !!doctorId && !!date,
staleTime: 2 * 60 * 1000, // 2 minutos - slots mudam frequentemente
...options,
});
}
// ============================================================================
// MUTATION HOOKS
// ============================================================================
/**
* Hook para criar nova disponibilidade
*/
export function useCreateAvailability() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: CreateAvailabilityInput) => {
return await availabilityService.create(data);
},
onSuccess: (data) => {
// Invalidar listas de disponibilidade
queryClient.invalidateQueries({ queryKey: availabilityKeys.lists() });
if (data?.doctor_id) {
queryClient.invalidateQueries({
queryKey: availabilityKeys.list(data.doctor_id),
});
}
toast.success("Disponibilidade criada com sucesso!");
},
onError: (error: Error) => {
toast.error(`Erro ao criar disponibilidade: ${error.message}`);
},
});
}
/**
* Hook para atualizar disponibilidade
*/
export function useUpdateAvailability() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: UpdateAvailabilityInput & { id: string }) => {
return await availabilityService.update(data.id, data);
},
onMutate: async (variables) => {
// Optimistic update
await queryClient.cancelQueries({
queryKey: availabilityKeys.detail(variables.id),
});
const previousAvailability = queryClient.getQueryData<DoctorAvailability>(
availabilityKeys.detail(variables.id)
);
return { previousAvailability };
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: availabilityKeys.lists() });
queryClient.invalidateQueries({
queryKey: availabilityKeys.detail(variables.id),
});
if (data?.doctor_id) {
queryClient.invalidateQueries({
queryKey: availabilityKeys.list(data.doctor_id),
});
}
toast.success("Disponibilidade atualizada com sucesso!");
},
onError: (error: Error, variables, context) => {
if (context?.previousAvailability) {
queryClient.setQueryData(
availabilityKeys.detail(variables.id),
context.previousAvailability
);
}
toast.error(`Erro ao atualizar: ${error.message}`);
},
});
}
/**
* Hook para deletar disponibilidade
*/
export function useDeleteAvailability() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id }: { id: string; doctorId: string }) => {
return await availabilityService.delete(id);
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: availabilityKeys.lists() });
queryClient.invalidateQueries({
queryKey: availabilityKeys.list(variables.doctorId),
});
toast.success("Disponibilidade removida com sucesso");
},
onError: (error: Error) => {
toast.error(`Erro ao remover: ${error.message}`);
},
});
}
// ============================================================================
// UTILITY HOOKS
// ============================================================================
/**
* Hook para prefetch de disponibilidade (otimização)
*/
export function usePrefetchAvailability() {
const queryClient = useQueryClient();
return (doctorId: string) => {
queryClient.prefetchQuery({
queryKey: availabilityKeys.list(doctorId),
queryFn: async () => {
return await availabilityService.list({ doctor_id: doctorId });
},
});
};
}
/**
* Hook para prefetch de slots disponíveis (navegação de calendário)
*/
export function usePrefetchAvailableSlots() {
const queryClient = useQueryClient();
return (doctorId: string, date: string) => {
queryClient.prefetchQuery({
queryKey: availabilityKeys.slots(doctorId, date),
queryFn: async () => {
await availabilityService.list({ doctor_id: doctorId });
// Lógica simplificada - ver hook useAvailableSlots para implementação completa
return [];
},
});
};
}