/** * Time Block Management Hooks * * Provides hooks for managing time blocks and holidays. * Time blocks allow businesses to block off time for closures, holidays, * resource unavailability, and recurring patterns. */ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import apiClient from '../api/client'; import { TimeBlock, TimeBlockListItem, BlockedDate, Holiday, TimeBlockConflictCheck, MyBlocksResponse, BlockType, RecurrenceType, RecurrencePattern, } from '../types'; // ============================================================================= // Interfaces // ============================================================================= export interface TimeBlockFilters { level?: 'business' | 'resource'; resource_id?: string; block_type?: BlockType; recurrence_type?: RecurrenceType; is_active?: boolean; } export interface BlockedDatesParams { start_date: string; end_date: string; resource_id?: string; include_business?: boolean; } export interface CreateTimeBlockData { title: string; description?: string; resource?: string | null; block_type: BlockType; recurrence_type: RecurrenceType; start_date?: string; end_date?: string; all_day?: boolean; start_time?: string; end_time?: string; recurrence_pattern?: RecurrencePattern; recurrence_start?: string; recurrence_end?: string; } export interface CheckConflictsData { recurrence_type: RecurrenceType; recurrence_pattern?: RecurrencePattern; start_date?: string; end_date?: string; resource_id?: string | null; all_day?: boolean; start_time?: string; end_time?: string; } // ============================================================================= // Time Block Hooks // ============================================================================= /** * Hook to fetch time blocks with optional filters */ export const useTimeBlocks = (filters?: TimeBlockFilters) => { return useQuery({ queryKey: ['time-blocks', filters], queryFn: async () => { const params = new URLSearchParams(); if (filters?.level) params.append('level', filters.level); if (filters?.resource_id) params.append('resource_id', filters.resource_id); if (filters?.block_type) params.append('block_type', filters.block_type); if (filters?.recurrence_type) params.append('recurrence_type', filters.recurrence_type); if (filters?.is_active !== undefined) params.append('is_active', String(filters.is_active)); const { data } = await apiClient.get(`/time-blocks/?${params}`); return data.map((block: any) => ({ ...block, id: String(block.id), resource: block.resource ? String(block.resource) : null, })); }, }); }; /** * Hook to get a single time block */ export const useTimeBlock = (id: string) => { return useQuery({ queryKey: ['time-blocks', id], queryFn: async () => { const { data } = await apiClient.get(`/time-blocks/${id}/`); return { ...data, id: String(data.id), resource: data.resource ? String(data.resource) : null, }; }, enabled: !!id, }); }; /** * Hook to get blocked dates for calendar visualization */ export const useBlockedDates = (params: BlockedDatesParams) => { return useQuery({ queryKey: ['blocked-dates', params], queryFn: async () => { const queryParams = new URLSearchParams({ start_date: params.start_date, end_date: params.end_date, }); if (params.resource_id) queryParams.append('resource_id', params.resource_id); if (params.include_business !== undefined) { queryParams.append('include_business', String(params.include_business)); } const url = `/time-blocks/blocked_dates/?${queryParams}`; const { data } = await apiClient.get(url); return data.blocked_dates.map((block: any) => ({ ...block, resource_id: block.resource_id ? String(block.resource_id) : null, time_block_id: String(block.time_block_id), })); }, enabled: !!params.start_date && !!params.end_date, }); }; /** * Hook to get time blocks for the current staff member */ export const useMyBlocks = () => { return useQuery({ queryKey: ['my-blocks'], queryFn: async () => { const { data } = await apiClient.get('/time-blocks/my_blocks/'); return { business_blocks: data.business_blocks.map((b: any) => ({ ...b, id: String(b.id), resource: b.resource ? String(b.resource) : null, })), my_blocks: data.my_blocks.map((b: any) => ({ ...b, id: String(b.id), resource: b.resource ? String(b.resource) : null, })), resource_id: data.resource_id ? String(data.resource_id) : null, resource_name: data.resource_name, can_self_approve: data.can_self_approve, }; }, }); }; /** * Hook to create a time block */ export const useCreateTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (blockData: CreateTimeBlockData) => { const payload = { ...blockData, resource: blockData.resource ? parseInt(blockData.resource) : null, }; const { data } = await apiClient.post('/time-blocks/', payload); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); }, }); }; /** * Hook to update a time block */ export const useUpdateTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, updates }: { id: string; updates: Partial }) => { const payload: any = { ...updates }; if (updates.resource !== undefined) { payload.resource = updates.resource ? parseInt(updates.resource) : null; } const { data } = await apiClient.patch(`/time-blocks/${id}/`, payload); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); }, }); }; /** * Hook to delete a time block */ export const useDeleteTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { await apiClient.delete(`/time-blocks/${id}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); }, }); }; /** * Hook to toggle a time block's active status */ export const useToggleTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { const { data } = await apiClient.post(`/time-blocks/${id}/toggle/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); }, }); }; // ============================================================================= // Time Block Approval Hooks // ============================================================================= export interface PendingReviewsResponse { count: number; pending_blocks: TimeBlockListItem[]; } /** * Hook to fetch pending time block reviews (for managers/owners) */ export const usePendingReviews = () => { return useQuery({ queryKey: ['time-block-pending-reviews'], queryFn: async () => { const { data } = await apiClient.get('/time-blocks/pending_reviews/'); return { count: data.count, pending_blocks: data.pending_blocks.map((b: any) => ({ ...b, id: String(b.id), resource: b.resource ? String(b.resource) : null, })), }; }, }); }; /** * Hook to approve a time block */ export const useApproveTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, notes }: { id: string; notes?: string }) => { const { data } = await apiClient.post(`/time-blocks/${id}/approve/`, { notes }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); queryClient.invalidateQueries({ queryKey: ['time-block-pending-reviews'] }); }, }); }; /** * Hook to deny a time block */ export const useDenyTimeBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, notes }: { id: string; notes?: string }) => { const { data } = await apiClient.post(`/time-blocks/${id}/deny/`, { notes }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['time-blocks'] }); queryClient.invalidateQueries({ queryKey: ['blocked-dates'] }); queryClient.invalidateQueries({ queryKey: ['my-blocks'] }); queryClient.invalidateQueries({ queryKey: ['time-block-pending-reviews'] }); }, }); }; /** * Hook to check for conflicts before creating a time block */ export const useCheckConflicts = () => { return useMutation({ mutationFn: async (checkData) => { const payload = { ...checkData, resource_id: checkData.resource_id ? parseInt(checkData.resource_id) : null, }; const { data } = await apiClient.post('/time-blocks/check_conflicts/', payload); return data; }, }); }; // ============================================================================= // Holiday Hooks // ============================================================================= /** * Hook to fetch holidays */ export const useHolidays = (country?: string) => { return useQuery({ queryKey: ['holidays', country], queryFn: async () => { const params = new URLSearchParams(); if (country) params.append('country', country); const { data } = await apiClient.get(`/holidays/?${params}`); return data; }, }); }; /** * Hook to get a single holiday by code */ export const useHoliday = (code: string) => { return useQuery({ queryKey: ['holidays', code], queryFn: async () => { const { data } = await apiClient.get(`/holidays/${code}/`); return data; }, enabled: !!code, }); }; /** * Hook to get holiday dates for a specific year */ export const useHolidayDates = (year?: number, country?: string) => { return useQuery<{ year: number; holidays: { code: string; name: string; date: string }[] }>({ queryKey: ['holiday-dates', year, country], queryFn: async () => { const params = new URLSearchParams(); if (year) params.append('year', String(year)); if (country) params.append('country', country); const { data } = await apiClient.get(`/holidays/dates/?${params}`); return data; }, }); };