/** * Customer Management Hooks */ import { useQuery, useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import apiClient from '../api/client'; import { Customer } from '../types'; interface CustomerFilters { status?: 'Active' | 'Inactive' | 'Blocked'; search?: string; } interface PaginatedResponse { count: number; next: string | null; previous: string | null; results: any[]; } /** * Transform backend customer data to frontend format */ const transformCustomer = (c: any): Customer => ({ id: String(c.id), name: c.name || c.user?.name || '', email: c.email || c.user?.email || '', phone: c.phone || '', city: c.city, state: c.state, zip: c.zip, totalSpend: parseFloat(c.total_spend || 0), lastVisit: c.last_visit ? new Date(c.last_visit) : null, status: c.status, avatarUrl: c.avatar_url, tags: c.tags || [], userId: String(c.user_id || c.user), paymentMethods: [], user_data: c.user_data, notes: c.notes || '', email_verified: c.email_verified ?? false, }); /** * Hook to fetch customers with infinite scroll pagination */ export const useCustomersInfinite = (filters?: CustomerFilters) => { return useInfiniteQuery({ queryKey: ['customers', 'infinite', filters || {}], queryFn: async ({ pageParam = 1 }) => { const params = new URLSearchParams(); params.append('page', String(pageParam)); params.append('page_size', '25'); if (filters?.status) params.append('status', filters.status); if (filters?.search) params.append('search', filters.search); const { data } = await apiClient.get(`/customers/?${params}`); // Handle both paginated and non-paginated responses if (Array.isArray(data)) { return { count: data.length, next: null, previous: null, results: data, }; } return data; }, initialPageParam: 1, getNextPageParam: (lastPage) => { if (lastPage.next) { // Extract page number from next URL const url = new URL(lastPage.next); const page = url.searchParams.get('page'); return page ? parseInt(page, 10) : undefined; } return undefined; }, retry: false, }); }; /** * Hook to fetch all customers (non-paginated, for backward compatibility) */ export const useCustomers = (filters?: CustomerFilters) => { return useQuery({ queryKey: ['customers', filters], queryFn: async () => { const params = new URLSearchParams(); if (filters?.status) params.append('status', filters.status); if (filters?.search) params.append('search', filters.search); const { data } = await apiClient.get(`/customers/?${params}`); // Handle paginated response const results = data.results || data; return results.map(transformCustomer); }, retry: false, }); }; interface CreateCustomerData { name?: string; firstName?: string; lastName?: string; email: string; phone?: string; city?: string; state?: string; zip?: string; } /** * Hook to create a customer */ export const useCreateCustomer = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (customerData: CreateCustomerData) => { // Parse name into first_name and last_name if provided as single field let firstName = customerData.firstName; let lastName = customerData.lastName; if (customerData.name && !firstName && !lastName) { const nameParts = customerData.name.trim().split(/\s+/); firstName = nameParts[0] || ''; lastName = nameParts.slice(1).join(' ') || ''; } const backendData = { first_name: firstName, last_name: lastName, email: customerData.email, phone: customerData.phone || '', // Note: city, state, zip are TODO in backend - not stored yet }; const { data } = await apiClient.post('/customers/', backendData); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers'] }); }, }); }; interface UpdateCustomerData { name?: string; firstName?: string; lastName?: string; email?: string; phone?: string; isActive?: boolean; notes?: string; } /** * Hook to update a customer */ export const useUpdateCustomer = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, updates }: { id: string; updates: UpdateCustomerData }) => { // Parse name into first_name and last_name if provided as single field let firstName = updates.firstName; let lastName = updates.lastName; if (updates.name && !firstName && !lastName) { const nameParts = updates.name.trim().split(/\s+/); firstName = nameParts[0] || ''; lastName = nameParts.slice(1).join(' ') || ''; } const backendData: Record = {}; if (firstName !== undefined) backendData.first_name = firstName; if (lastName !== undefined) backendData.last_name = lastName; if (updates.email !== undefined) backendData.email = updates.email; if (updates.phone !== undefined) backendData.phone = updates.phone; if (updates.isActive !== undefined) backendData.is_active = updates.isActive; if (updates.notes !== undefined) backendData.notes = updates.notes; const { data } = await apiClient.patch(`/customers/${id}/`, backendData); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers'] }); }, }); }; /** * Hook to delete a customer */ export const useDeleteCustomer = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { await apiClient.delete(`/customers/${id}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers'] }); }, }); }; /** * Hook to verify a customer's email address */ export const useVerifyCustomerEmail = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { const { data } = await apiClient.post(`/customers/${id}/verify_email/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['customers'] }); }, }); };