forked from RiseUP/riseup-squad18
279 lines
8.3 KiB
TypeScript
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 [];
|
|
},
|
|
});
|
|
};
|
|
}
|