/** * Contract Management Hooks */ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import apiClient from '../api/client'; import { ContractTemplate, Contract, ContractPublicView, ContractScope, ContractTemplateStatus, } from '../types'; // --- Contract Templates --- /** * Hook to fetch all contract templates for current business */ export const useContractTemplates = (status?: ContractTemplateStatus) => { return useQuery({ queryKey: ['contract-templates', status], queryFn: async () => { const params = status ? { status } : {}; const { data } = await apiClient.get('/contracts/templates/', { params }); return data.map((t: any) => ({ id: String(t.id), name: t.name, description: t.description || '', content: t.content, scope: t.scope as ContractScope, status: t.status as ContractTemplateStatus, expires_after_days: t.expires_after_days, version: t.version, version_notes: t.version_notes || '', services: t.services || [], created_by: t.created_by ? String(t.created_by) : null, created_by_name: t.created_by_name || null, created_at: t.created_at, updated_at: t.updated_at, })); }, retry: false, }); }; /** * Hook to get a single contract template */ export const useContractTemplate = (id: string) => { return useQuery({ queryKey: ['contract-templates', id], queryFn: async () => { const { data } = await apiClient.get(`/contracts/templates/${id}/`); return { id: String(data.id), name: data.name, description: data.description || '', content: data.content, scope: data.scope as ContractScope, status: data.status as ContractTemplateStatus, expires_after_days: data.expires_after_days, version: data.version, version_notes: data.version_notes || '', services: data.services || [], created_by: data.created_by ? String(data.created_by) : null, created_by_name: data.created_by_name || null, created_at: data.created_at, updated_at: data.updated_at, }; }, enabled: !!id, retry: false, }); }; interface ContractTemplateInput { name: string; description?: string; content: string; scope: ContractScope; status?: ContractTemplateStatus; expires_after_days?: number | null; version_notes?: string; services?: string[]; } /** * Hook to create a contract template */ export const useCreateContractTemplate = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (templateData: ContractTemplateInput) => { const { data } = await apiClient.post('/contracts/templates/', templateData); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract-templates'] }); }, }); }; /** * Hook to update a contract template */ export const useUpdateContractTemplate = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, updates, }: { id: string; updates: Partial; }) => { const { data } = await apiClient.patch(`/contracts/templates/${id}/`, updates); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract-templates'] }); }, }); }; /** * Hook to delete a contract template */ export const useDeleteContractTemplate = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { await apiClient.delete(`/contracts/templates/${id}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract-templates'] }); }, }); }; /** * Hook to duplicate a contract template */ export const useDuplicateContractTemplate = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { const { data } = await apiClient.post(`/contracts/templates/${id}/duplicate/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract-templates'] }); }, }); }; /** * Hook to preview a contract template */ export const usePreviewContractTemplate = () => { return useMutation({ mutationFn: async ({ id, context, }: { id: string; context?: Record; }) => { const { data } = await apiClient.post( `/contracts/templates/${id}/preview/`, context || {} ); return data; }, }); }; // --- Contracts --- /** * Hook to fetch all contracts for current business */ export const useContracts = (filters?: { status?: string; customer?: string; appointment?: string; }) => { return useQuery({ queryKey: ['contracts', filters], queryFn: async () => { const { data } = await apiClient.get('/contracts/', { params: filters, }); return data.map((c: any) => ({ id: String(c.id), template: String(c.template), template_name: c.template_name, template_version: c.template_version, scope: c.scope as ContractScope, status: c.status, content: c.content, customer: c.customer ? String(c.customer) : undefined, customer_name: c.customer_name || undefined, customer_email: c.customer_email || undefined, appointment: c.appointment ? String(c.appointment) : undefined, appointment_service_name: c.appointment_service_name || undefined, appointment_start_time: c.appointment_start_time || undefined, service: c.service ? String(c.service) : undefined, service_name: c.service_name || undefined, sent_at: c.sent_at, signed_at: c.signed_at, expires_at: c.expires_at, voided_at: c.voided_at, voided_reason: c.voided_reason, public_token: c.public_token, created_at: c.created_at, updated_at: c.updated_at, })); }, retry: false, }); }; /** * Hook to get a single contract */ export const useContract = (id: string) => { return useQuery({ queryKey: ['contracts', id], queryFn: async () => { const { data } = await apiClient.get(`/contracts/${id}/`); return { id: String(data.id), template: String(data.template), template_name: data.template_name, template_version: data.template_version, scope: data.scope as ContractScope, status: data.status, content: data.content, customer: data.customer ? String(data.customer) : undefined, customer_name: data.customer_name || undefined, customer_email: data.customer_email || undefined, appointment: data.appointment ? String(data.appointment) : undefined, appointment_service_name: data.appointment_service_name || undefined, appointment_start_time: data.appointment_start_time || undefined, service: data.service ? String(data.service) : undefined, service_name: data.service_name || undefined, sent_at: data.sent_at, signed_at: data.signed_at, expires_at: data.expires_at, voided_at: data.voided_at, voided_reason: data.voided_reason, public_token: data.public_token, created_at: data.created_at, updated_at: data.updated_at, }; }, enabled: !!id, retry: false, }); }; interface ContractInput { template: string; customer?: string; appointment?: string; service?: string; } /** * Hook to create a contract */ export const useCreateContract = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (contractData: ContractInput) => { const { data } = await apiClient.post('/contracts/', contractData); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contracts'] }); }, }); }; /** * Hook to send a contract to customer */ export const useSendContract = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { const { data } = await apiClient.post(`/contracts/${id}/send/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contracts'] }); }, }); }; /** * Hook to void a contract */ export const useVoidContract = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, reason }: { id: string; reason: string }) => { const { data } = await apiClient.post(`/contracts/${id}/void/`, { reason }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contracts'] }); }, }); }; /** * Hook to resend a contract */ export const useResendContract = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { const { data } = await apiClient.post(`/contracts/${id}/resend/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contracts'] }); }, }); }; // --- Public Contract Access (no auth required) --- /** * Hook to fetch public contract view by token (no auth required) */ export const usePublicContract = (token: string) => { return useQuery({ queryKey: ['public-contracts', token], queryFn: async () => { // Use a plain axios instance without auth const { data } = await apiClient.get(`/contracts/public/${token}/`); return data; }, enabled: !!token, retry: false, }); }; /** * Hook to sign a contract (no auth required) */ export const useSignContract = () => { return useMutation({ mutationFn: async ({ token, signature_data, signer_name, signer_email, }: { token: string; signature_data: string; signer_name: string; signer_email: string; }) => { const { data } = await apiClient.post(`/contracts/public/${token}/sign/`, { signature_data, signer_name, signer_email, }); return data; }, }); };