2025-11-23 21:43:13 +00:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
|
import { useCallback } from "react";
|
|
|
|
|
import {
|
|
|
|
|
createAppointment,
|
|
|
|
|
getAvailableDates,
|
|
|
|
|
listAppointments,
|
|
|
|
|
getUserAppointments,
|
2025-11-25 21:31:20 +00:00
|
|
|
getUserAppointmentStats,
|
2025-11-23 21:43:13 +00:00
|
|
|
getAppointmentDetail,
|
|
|
|
|
scheduleAppointment,
|
|
|
|
|
rejectAppointment,
|
|
|
|
|
getAdminAvailability,
|
|
|
|
|
updateAdminAvailability,
|
|
|
|
|
getAppointmentStats,
|
|
|
|
|
getJitsiMeetingInfo,
|
2025-11-27 19:18:59 +00:00
|
|
|
getWeeklyAvailability,
|
|
|
|
|
getAvailabilityConfig,
|
|
|
|
|
checkDateAvailability,
|
|
|
|
|
getAvailabilityOverview,
|
2025-11-23 21:43:13 +00:00
|
|
|
} from "@/lib/actions/appointments";
|
|
|
|
|
import type {
|
|
|
|
|
CreateAppointmentInput,
|
|
|
|
|
ScheduleAppointmentInput,
|
|
|
|
|
RejectAppointmentInput,
|
|
|
|
|
UpdateAvailabilityInput,
|
|
|
|
|
} from "@/lib/schema/appointments";
|
|
|
|
|
import type {
|
|
|
|
|
Appointment,
|
|
|
|
|
AdminAvailability,
|
|
|
|
|
AppointmentStats,
|
2025-11-25 21:31:20 +00:00
|
|
|
UserAppointmentStats,
|
|
|
|
|
AvailableDatesResponse,
|
2025-11-23 21:43:13 +00:00
|
|
|
JitsiMeetingInfo,
|
2025-11-27 19:18:59 +00:00
|
|
|
WeeklyAvailabilityResponse,
|
|
|
|
|
AvailabilityConfig,
|
|
|
|
|
CheckDateAvailabilityResponse,
|
|
|
|
|
AvailabilityOverview,
|
2025-11-23 21:43:13 +00:00
|
|
|
} from "@/lib/models/appointments";
|
|
|
|
|
|
2025-11-27 19:18:59 +00:00
|
|
|
export function useAppointments(options?: {
|
|
|
|
|
enableAvailableDates?: boolean;
|
|
|
|
|
enableStats?: boolean;
|
|
|
|
|
enableConfig?: boolean;
|
|
|
|
|
enableWeeklyAvailability?: boolean;
|
|
|
|
|
enableOverview?: boolean;
|
|
|
|
|
}) {
|
2025-11-23 21:43:13 +00:00
|
|
|
const queryClient = useQueryClient();
|
2025-11-27 19:18:59 +00:00
|
|
|
const enableAvailableDates = options?.enableAvailableDates ?? false;
|
|
|
|
|
const enableStats = options?.enableStats ?? true;
|
|
|
|
|
const enableConfig = options?.enableConfig ?? true;
|
|
|
|
|
const enableWeeklyAvailability = options?.enableWeeklyAvailability ?? true;
|
|
|
|
|
const enableOverview = options?.enableOverview ?? true;
|
2025-11-23 21:43:13 +00:00
|
|
|
|
2025-11-27 19:18:59 +00:00
|
|
|
// Get available dates query (optional, disabled by default - using weekly_availability as primary source)
|
|
|
|
|
// Can be enabled when explicitly needed (e.g., on admin booking page)
|
2025-11-25 21:04:22 +00:00
|
|
|
const availableDatesQuery = useQuery<AvailableDatesResponse>({
|
2025-11-23 21:43:13 +00:00
|
|
|
queryKey: ["appointments", "available-dates"],
|
|
|
|
|
queryFn: () => getAvailableDates(),
|
2025-11-27 19:18:59 +00:00
|
|
|
enabled: enableAvailableDates, // Can be enabled when needed
|
|
|
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
|
|
|
retry: 0, // Don't retry failed requests
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get weekly availability query
|
|
|
|
|
const weeklyAvailabilityQuery = useQuery<WeeklyAvailabilityResponse>({
|
|
|
|
|
queryKey: ["appointments", "weekly-availability"],
|
|
|
|
|
queryFn: async () => {
|
|
|
|
|
const data = await getWeeklyAvailability();
|
|
|
|
|
// Normalize response format - ensure it's always an object with week array
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
|
return { week: data };
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
enabled: enableWeeklyAvailability,
|
|
|
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get availability config query
|
|
|
|
|
const availabilityConfigQuery = useQuery<AvailabilityConfig>({
|
|
|
|
|
queryKey: ["appointments", "availability-config"],
|
|
|
|
|
queryFn: () => getAvailabilityConfig(),
|
|
|
|
|
enabled: enableConfig,
|
|
|
|
|
staleTime: 60 * 60 * 1000, // 1 hour (config rarely changes)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get availability overview query
|
|
|
|
|
const availabilityOverviewQuery = useQuery<AvailabilityOverview>({
|
|
|
|
|
queryKey: ["appointments", "availability-overview"],
|
|
|
|
|
queryFn: () => getAvailabilityOverview(),
|
|
|
|
|
enabled: enableOverview,
|
2025-11-23 21:43:13 +00:00
|
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// List appointments query
|
|
|
|
|
const appointmentsQuery = useQuery<Appointment[]>({
|
|
|
|
|
queryKey: ["appointments", "list"],
|
2025-11-27 19:18:59 +00:00
|
|
|
queryFn: async () => {
|
|
|
|
|
const data = await listAppointments();
|
|
|
|
|
return data || [];
|
|
|
|
|
},
|
|
|
|
|
enabled: true, // Enable by default to fetch user appointments
|
|
|
|
|
staleTime: 30 * 1000, // 30 seconds
|
|
|
|
|
retry: 1, // Retry once on failure
|
|
|
|
|
refetchOnMount: true, // Always refetch when component mounts
|
2025-11-23 21:43:13 +00:00
|
|
|
});
|
|
|
|
|
|
2025-11-27 19:18:59 +00:00
|
|
|
// Get user appointments query (disabled - using listAppointments instead)
|
2025-11-23 21:43:13 +00:00
|
|
|
const userAppointmentsQuery = useQuery<Appointment[]>({
|
|
|
|
|
queryKey: ["appointments", "user"],
|
|
|
|
|
queryFn: () => getUserAppointments(),
|
2025-11-27 19:18:59 +00:00
|
|
|
enabled: false, // Disabled - using listAppointments endpoint instead
|
2025-11-23 21:43:13 +00:00
|
|
|
staleTime: 30 * 1000, // 30 seconds
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get appointment detail query
|
|
|
|
|
const useAppointmentDetail = (id: string | null) => {
|
|
|
|
|
return useQuery<Appointment>({
|
|
|
|
|
queryKey: ["appointments", "detail", id],
|
|
|
|
|
queryFn: () => getAppointmentDetail(id!),
|
|
|
|
|
enabled: !!id,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Get admin availability query
|
|
|
|
|
const adminAvailabilityQuery = useQuery<AdminAvailability>({
|
|
|
|
|
queryKey: ["appointments", "admin", "availability"],
|
|
|
|
|
queryFn: () => getAdminAvailability(),
|
|
|
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get appointment stats query
|
|
|
|
|
const appointmentStatsQuery = useQuery<AppointmentStats>({
|
|
|
|
|
queryKey: ["appointments", "stats"],
|
|
|
|
|
queryFn: () => getAppointmentStats(),
|
|
|
|
|
staleTime: 1 * 60 * 1000, // 1 minute
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-25 21:25:53 +00:00
|
|
|
// Get user appointment stats query
|
|
|
|
|
const userAppointmentStatsQuery = useQuery<UserAppointmentStats>({
|
|
|
|
|
queryKey: ["appointments", "user", "stats"],
|
|
|
|
|
queryFn: () => getUserAppointmentStats(),
|
2025-11-27 19:18:59 +00:00
|
|
|
enabled: enableStats,
|
2025-11-25 21:25:53 +00:00
|
|
|
staleTime: 1 * 60 * 1000, // 1 minute
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-23 21:43:13 +00:00
|
|
|
// Get Jitsi meeting info query
|
|
|
|
|
const useJitsiMeetingInfo = (id: string | null) => {
|
|
|
|
|
return useQuery<JitsiMeetingInfo>({
|
|
|
|
|
queryKey: ["appointments", "jitsi", id],
|
|
|
|
|
queryFn: () => getJitsiMeetingInfo(id!),
|
|
|
|
|
enabled: !!id,
|
|
|
|
|
staleTime: 30 * 1000, // 30 seconds
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create appointment mutation
|
|
|
|
|
const createAppointmentMutation = useMutation({
|
|
|
|
|
mutationFn: (input: CreateAppointmentInput) => createAppointment(input),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["appointments"] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Schedule appointment mutation
|
|
|
|
|
const scheduleAppointmentMutation = useMutation({
|
|
|
|
|
mutationFn: ({ id, input }: { id: string; input: ScheduleAppointmentInput }) =>
|
|
|
|
|
scheduleAppointment(id, input),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["appointments"] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Reject appointment mutation
|
|
|
|
|
const rejectAppointmentMutation = useMutation({
|
|
|
|
|
mutationFn: ({ id, input }: { id: string; input: RejectAppointmentInput }) =>
|
|
|
|
|
rejectAppointment(id, input),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["appointments"] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update admin availability mutation
|
|
|
|
|
const updateAdminAvailabilityMutation = useMutation({
|
|
|
|
|
mutationFn: (input: UpdateAvailabilityInput) => updateAdminAvailability(input),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["appointments", "admin", "availability"] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["appointments", "available-dates"] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Convenience functions
|
|
|
|
|
const create = useCallback(
|
|
|
|
|
async (input: CreateAppointmentInput) => {
|
|
|
|
|
return await createAppointmentMutation.mutateAsync(input);
|
|
|
|
|
},
|
|
|
|
|
[createAppointmentMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const schedule = useCallback(
|
|
|
|
|
async (id: string, input: ScheduleAppointmentInput) => {
|
|
|
|
|
return await scheduleAppointmentMutation.mutateAsync({ id, input });
|
|
|
|
|
},
|
|
|
|
|
[scheduleAppointmentMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const reject = useCallback(
|
|
|
|
|
async (id: string, input: RejectAppointmentInput) => {
|
|
|
|
|
return await rejectAppointmentMutation.mutateAsync({ id, input });
|
|
|
|
|
},
|
|
|
|
|
[rejectAppointmentMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const updateAvailability = useCallback(
|
|
|
|
|
async (input: UpdateAvailabilityInput) => {
|
|
|
|
|
return await updateAdminAvailabilityMutation.mutateAsync(input);
|
|
|
|
|
},
|
|
|
|
|
[updateAdminAvailabilityMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const fetchAppointments = useCallback(
|
|
|
|
|
async (email?: string) => {
|
|
|
|
|
const data = await listAppointments(email);
|
|
|
|
|
queryClient.setQueryData(["appointments", "list"], data);
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
[queryClient]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// Queries
|
2025-11-25 21:04:22 +00:00
|
|
|
availableDates: availableDatesQuery.data?.dates || [],
|
|
|
|
|
availableDatesResponse: availableDatesQuery.data,
|
2025-11-27 19:18:59 +00:00
|
|
|
weeklyAvailability: weeklyAvailabilityQuery.data,
|
|
|
|
|
availabilityConfig: availabilityConfigQuery.data,
|
|
|
|
|
availabilityOverview: availabilityOverviewQuery.data,
|
2025-11-23 21:43:13 +00:00
|
|
|
appointments: appointmentsQuery.data || [],
|
|
|
|
|
userAppointments: userAppointmentsQuery.data || [],
|
|
|
|
|
adminAvailability: adminAvailabilityQuery.data,
|
|
|
|
|
appointmentStats: appointmentStatsQuery.data,
|
2025-11-25 21:25:53 +00:00
|
|
|
userAppointmentStats: userAppointmentStatsQuery.data,
|
2025-11-23 21:43:13 +00:00
|
|
|
|
|
|
|
|
// Query states
|
|
|
|
|
isLoadingAvailableDates: availableDatesQuery.isLoading,
|
2025-11-27 19:18:59 +00:00
|
|
|
isLoadingWeeklyAvailability: weeklyAvailabilityQuery.isLoading,
|
|
|
|
|
isLoadingAvailabilityConfig: availabilityConfigQuery.isLoading,
|
|
|
|
|
isLoadingAvailabilityOverview: availabilityOverviewQuery.isLoading,
|
2025-11-23 21:43:13 +00:00
|
|
|
isLoadingAppointments: appointmentsQuery.isLoading,
|
|
|
|
|
isLoadingUserAppointments: userAppointmentsQuery.isLoading,
|
|
|
|
|
isLoadingAdminAvailability: adminAvailabilityQuery.isLoading,
|
|
|
|
|
isLoadingStats: appointmentStatsQuery.isLoading,
|
2025-11-25 21:25:53 +00:00
|
|
|
isLoadingUserStats: userAppointmentStatsQuery.isLoading,
|
2025-11-23 21:43:13 +00:00
|
|
|
|
|
|
|
|
// Query refetch functions
|
|
|
|
|
refetchAvailableDates: availableDatesQuery.refetch,
|
2025-11-27 19:18:59 +00:00
|
|
|
refetchWeeklyAvailability: weeklyAvailabilityQuery.refetch,
|
|
|
|
|
refetchAvailabilityConfig: availabilityConfigQuery.refetch,
|
|
|
|
|
refetchAvailabilityOverview: availabilityOverviewQuery.refetch,
|
2025-11-23 21:43:13 +00:00
|
|
|
refetchAppointments: appointmentsQuery.refetch,
|
|
|
|
|
refetchUserAppointments: userAppointmentsQuery.refetch,
|
|
|
|
|
refetchAdminAvailability: adminAvailabilityQuery.refetch,
|
|
|
|
|
refetchStats: appointmentStatsQuery.refetch,
|
2025-11-25 21:25:53 +00:00
|
|
|
refetchUserStats: userAppointmentStatsQuery.refetch,
|
2025-11-23 21:43:13 +00:00
|
|
|
|
2025-11-27 19:18:59 +00:00
|
|
|
// Helper functions
|
|
|
|
|
checkDateAvailability: async (date: string) => {
|
|
|
|
|
return await checkDateAvailability(date);
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-23 21:43:13 +00:00
|
|
|
// Hooks for specific queries
|
|
|
|
|
useAppointmentDetail,
|
|
|
|
|
useJitsiMeetingInfo,
|
|
|
|
|
|
|
|
|
|
// Mutations
|
|
|
|
|
create,
|
|
|
|
|
schedule,
|
|
|
|
|
reject,
|
|
|
|
|
updateAvailability,
|
|
|
|
|
fetchAppointments,
|
|
|
|
|
|
|
|
|
|
// Mutation states
|
|
|
|
|
isCreating: createAppointmentMutation.isPending,
|
|
|
|
|
isScheduling: scheduleAppointmentMutation.isPending,
|
|
|
|
|
isRejecting: rejectAppointmentMutation.isPending,
|
|
|
|
|
isUpdatingAvailability: updateAdminAvailabilityMutation.isPending,
|
|
|
|
|
|
|
|
|
|
// Direct mutation access (if needed)
|
|
|
|
|
createAppointmentMutation,
|
|
|
|
|
scheduleAppointmentMutation,
|
|
|
|
|
rejectAppointmentMutation,
|
|
|
|
|
updateAdminAvailabilityMutation,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|