// frontend/src/api/hooks/useSuppliers.ts /** * React hooks for suppliers, purchase orders, and deliveries management */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { SuppliersService, Supplier, SupplierSummary, CreateSupplierRequest, UpdateSupplierRequest, SupplierSearchParams, SupplierStatistics, PurchaseOrder, CreatePurchaseOrderRequest, PurchaseOrderSearchParams, PurchaseOrderStatistics, Delivery, DeliverySearchParams, DeliveryPerformanceStats } from '../services/suppliers.service'; import { useAuth } from './useAuth'; // Re-export types for component use export type { Supplier, SupplierSummary, CreateSupplierRequest, UpdateSupplierRequest, SupplierSearchParams, SupplierStatistics, PurchaseOrder, CreatePurchaseOrderRequest, PurchaseOrderSearchParams, PurchaseOrderStatistics, Delivery, DeliverySearchParams, DeliveryPerformanceStats } from '../services/suppliers.service'; const suppliersService = new SuppliersService(); // ============================================================================ // SUPPLIERS HOOK // ============================================================================ export interface UseSuppliers { // Data suppliers: SupplierSummary[]; supplier: Supplier | null; statistics: SupplierStatistics | null; activeSuppliers: SupplierSummary[]; topSuppliers: SupplierSummary[]; suppliersNeedingReview: SupplierSummary[]; // States isLoading: boolean; isCreating: boolean; isUpdating: boolean; error: string | null; // Pagination pagination: { page: number; limit: number; total: number; totalPages: number; }; // Actions loadSuppliers: (params?: SupplierSearchParams) => Promise; loadSupplier: (supplierId: string) => Promise; loadStatistics: () => Promise; loadActiveSuppliers: () => Promise; loadTopSuppliers: (limit?: number) => Promise; loadSuppliersNeedingReview: (days?: number) => Promise; createSupplier: (data: CreateSupplierRequest) => Promise; updateSupplier: (supplierId: string, data: UpdateSupplierRequest) => Promise; deleteSupplier: (supplierId: string) => Promise; approveSupplier: (supplierId: string, action: 'approve' | 'reject', notes?: string) => Promise; clearError: () => void; refresh: () => Promise; setPage: (page: number) => void; } export function useSuppliers(): UseSuppliers { const { user } = useAuth(); // State const [suppliers, setSuppliers] = useState([]); const [supplier, setSupplier] = useState(null); const [statistics, setStatistics] = useState(null); const [activeSuppliers, setActiveSuppliers] = useState([]); const [topSuppliers, setTopSuppliers] = useState([]); const [suppliersNeedingReview, setSuppliersNeedingReview] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [error, setError] = useState(null); const [currentParams, setCurrentParams] = useState({}); const [pagination, setPagination] = useState({ page: 1, limit: 50, total: 0, totalPages: 0 }); // Load suppliers const loadSuppliers = useCallback(async (params: SupplierSearchParams = {}) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const searchParams = { ...params, limit: pagination.limit, offset: ((params.offset !== undefined ? Math.floor(params.offset / pagination.limit) : pagination.page) - 1) * pagination.limit }; setCurrentParams(params); const data = await suppliersService.getSuppliers(user.tenant_id, searchParams); setSuppliers(data); // Update pagination (Note: API doesn't return total count, so we estimate) const hasMore = data.length === pagination.limit; const currentPage = Math.floor((searchParams.offset || 0) / pagination.limit) + 1; setPagination(prev => ({ ...prev, page: currentPage, total: hasMore ? (currentPage * pagination.limit) + 1 : (currentPage - 1) * pagination.limit + data.length, totalPages: hasMore ? currentPage + 1 : currentPage })); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load suppliers'); } finally { setIsLoading(false); } }, [user?.tenant_id, pagination.limit]); // Load single supplier const loadSupplier = useCallback(async (supplierId: string) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const data = await suppliersService.getSupplier(user.tenant_id, supplierId); setSupplier(data); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load supplier'); } finally { setIsLoading(false); } }, [user?.tenant_id]); // Load statistics const loadStatistics = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getSupplierStatistics(user.tenant_id); setStatistics(data); } catch (err: any) { console.error('Failed to load supplier statistics:', err); } }, [user?.tenant_id]); // Load active suppliers const loadActiveSuppliers = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getActiveSuppliers(user.tenant_id); setActiveSuppliers(data); } catch (err: any) { console.error('Failed to load active suppliers:', err); } }, [user?.tenant_id]); // Load top suppliers const loadTopSuppliers = useCallback(async (limit: number = 10) => { if (!user?.tenant_id) return; try { const data = await suppliersService.getTopSuppliers(user.tenant_id, limit); setTopSuppliers(data); } catch (err: any) { console.error('Failed to load top suppliers:', err); } }, [user?.tenant_id]); // Load suppliers needing review const loadSuppliersNeedingReview = useCallback(async (days: number = 30) => { if (!user?.tenant_id) return; try { const data = await suppliersService.getSuppliersNeedingReview(user.tenant_id, days); setSuppliersNeedingReview(data); } catch (err: any) { console.error('Failed to load suppliers needing review:', err); } }, [user?.tenant_id]); // Create supplier const createSupplier = useCallback(async (data: CreateSupplierRequest): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setIsCreating(true); setError(null); const supplier = await suppliersService.createSupplier(user.tenant_id, user.id, data); // Refresh suppliers list await loadSuppliers(currentParams); await loadStatistics(); return supplier; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to create supplier'; setError(errorMessage); return null; } finally { setIsCreating(false); } }, [user?.tenant_id, user?.id, loadSuppliers, loadStatistics, currentParams]); // Update supplier const updateSupplier = useCallback(async (supplierId: string, data: UpdateSupplierRequest): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setIsUpdating(true); setError(null); const updatedSupplier = await suppliersService.updateSupplier(user.tenant_id, user.id, supplierId, data); // Update current supplier if it's the one being edited if (supplier?.id === supplierId) { setSupplier(updatedSupplier); } // Refresh suppliers list await loadSuppliers(currentParams); return updatedSupplier; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to update supplier'; setError(errorMessage); return null; } finally { setIsUpdating(false); } }, [user?.tenant_id, user?.id, supplier?.id, loadSuppliers, currentParams]); // Delete supplier const deleteSupplier = useCallback(async (supplierId: string): Promise => { if (!user?.tenant_id) return false; try { setError(null); await suppliersService.deleteSupplier(user.tenant_id, supplierId); // Clear current supplier if it's the one being deleted if (supplier?.id === supplierId) { setSupplier(null); } // Refresh suppliers list await loadSuppliers(currentParams); await loadStatistics(); return true; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to delete supplier'; setError(errorMessage); return false; } }, [user?.tenant_id, supplier?.id, loadSuppliers, loadStatistics, currentParams]); // Approve/reject supplier const approveSupplier = useCallback(async (supplierId: string, action: 'approve' | 'reject', notes?: string): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedSupplier = await suppliersService.approveSupplier(user.tenant_id, user.id, supplierId, action, notes); // Update current supplier if it's the one being approved/rejected if (supplier?.id === supplierId) { setSupplier(updatedSupplier); } // Refresh suppliers list and statistics await loadSuppliers(currentParams); await loadStatistics(); return updatedSupplier; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || `Failed to ${action} supplier`; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, supplier?.id, loadSuppliers, loadStatistics, currentParams]); // Clear error const clearError = useCallback(() => { setError(null); }, []); // Refresh current data const refresh = useCallback(async () => { await loadSuppliers(currentParams); if (statistics) await loadStatistics(); if (activeSuppliers.length > 0) await loadActiveSuppliers(); if (topSuppliers.length > 0) await loadTopSuppliers(); if (suppliersNeedingReview.length > 0) await loadSuppliersNeedingReview(); }, [currentParams, statistics, activeSuppliers.length, topSuppliers.length, suppliersNeedingReview.length, loadSuppliers, loadStatistics, loadActiveSuppliers, loadTopSuppliers, loadSuppliersNeedingReview]); // Set page const setPage = useCallback((page: number) => { setPagination(prev => ({ ...prev, page })); const offset = (page - 1) * pagination.limit; loadSuppliers({ ...currentParams, offset }); }, [pagination.limit, currentParams, loadSuppliers]); return { // Data suppliers, supplier, statistics, activeSuppliers, topSuppliers, suppliersNeedingReview, // States isLoading, isCreating, isUpdating, error, // Pagination pagination, // Actions loadSuppliers, loadSupplier, loadStatistics, loadActiveSuppliers, loadTopSuppliers, loadSuppliersNeedingReview, createSupplier, updateSupplier, deleteSupplier, approveSupplier, clearError, refresh, setPage }; } // ============================================================================ // PURCHASE ORDERS HOOK // ============================================================================ export interface UsePurchaseOrders { purchaseOrders: PurchaseOrder[]; purchaseOrder: PurchaseOrder | null; statistics: PurchaseOrderStatistics | null; ordersRequiringApproval: PurchaseOrder[]; overdueOrders: PurchaseOrder[]; isLoading: boolean; isCreating: boolean; error: string | null; pagination: { page: number; limit: number; total: number; totalPages: number; }; loadPurchaseOrders: (params?: PurchaseOrderSearchParams) => Promise; loadPurchaseOrder: (poId: string) => Promise; loadStatistics: () => Promise; loadOrdersRequiringApproval: () => Promise; loadOverdueOrders: () => Promise; createPurchaseOrder: (data: CreatePurchaseOrderRequest) => Promise; updateOrderStatus: (poId: string, status: string, notes?: string) => Promise; approveOrder: (poId: string, action: 'approve' | 'reject', notes?: string) => Promise; sendToSupplier: (poId: string, sendEmail?: boolean) => Promise; cancelOrder: (poId: string, reason: string) => Promise; clearError: () => void; refresh: () => Promise; setPage: (page: number) => void; } export function usePurchaseOrders(): UsePurchaseOrders { const { user } = useAuth(); // State const [purchaseOrders, setPurchaseOrders] = useState([]); const [purchaseOrder, setPurchaseOrder] = useState(null); const [statistics, setStatistics] = useState(null); const [ordersRequiringApproval, setOrdersRequiringApproval] = useState([]); const [overdueOrders, setOverdueOrders] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(null); const [currentParams, setCurrentParams] = useState({}); const [pagination, setPagination] = useState({ page: 1, limit: 50, total: 0, totalPages: 0 }); // Load purchase orders const loadPurchaseOrders = useCallback(async (params: PurchaseOrderSearchParams = {}) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const searchParams = { ...params, limit: pagination.limit, offset: ((params.offset !== undefined ? Math.floor(params.offset / pagination.limit) : pagination.page) - 1) * pagination.limit }; setCurrentParams(params); const data = await suppliersService.getPurchaseOrders(user.tenant_id, searchParams); setPurchaseOrders(data); // Update pagination const hasMore = data.length === pagination.limit; const currentPage = Math.floor((searchParams.offset || 0) / pagination.limit) + 1; setPagination(prev => ({ ...prev, page: currentPage, total: hasMore ? (currentPage * pagination.limit) + 1 : (currentPage - 1) * pagination.limit + data.length, totalPages: hasMore ? currentPage + 1 : currentPage })); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load purchase orders'); } finally { setIsLoading(false); } }, [user?.tenant_id, pagination.limit]); // Other purchase order methods... const loadPurchaseOrder = useCallback(async (poId: string) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const data = await suppliersService.getPurchaseOrder(user.tenant_id, poId); setPurchaseOrder(data); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load purchase order'); } finally { setIsLoading(false); } }, [user?.tenant_id]); const loadStatistics = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getPurchaseOrderStatistics(user.tenant_id); setStatistics(data); } catch (err: any) { console.error('Failed to load purchase order statistics:', err); } }, [user?.tenant_id]); const loadOrdersRequiringApproval = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getOrdersRequiringApproval(user.tenant_id); setOrdersRequiringApproval(data); } catch (err: any) { console.error('Failed to load orders requiring approval:', err); } }, [user?.tenant_id]); const loadOverdueOrders = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getOverdueOrders(user.tenant_id); setOverdueOrders(data); } catch (err: any) { console.error('Failed to load overdue orders:', err); } }, [user?.tenant_id]); const createPurchaseOrder = useCallback(async (data: CreatePurchaseOrderRequest): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setIsCreating(true); setError(null); const order = await suppliersService.createPurchaseOrder(user.tenant_id, user.id, data); // Refresh orders list await loadPurchaseOrders(currentParams); await loadStatistics(); return order; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to create purchase order'; setError(errorMessage); return null; } finally { setIsCreating(false); } }, [user?.tenant_id, user?.id, loadPurchaseOrders, loadStatistics, currentParams]); const updateOrderStatus = useCallback(async (poId: string, status: string, notes?: string): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedOrder = await suppliersService.updatePurchaseOrderStatus(user.tenant_id, user.id, poId, status, notes); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); } await loadPurchaseOrders(currentParams); return updatedOrder; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to update order status'; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const approveOrder = useCallback(async (poId: string, action: 'approve' | 'reject', notes?: string): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedOrder = await suppliersService.approvePurchaseOrder(user.tenant_id, user.id, poId, action, notes); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); } await loadPurchaseOrders(currentParams); await loadOrdersRequiringApproval(); return updatedOrder; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || `Failed to ${action} order`; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, loadOrdersRequiringApproval, currentParams]); const sendToSupplier = useCallback(async (poId: string, sendEmail: boolean = true): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedOrder = await suppliersService.sendToSupplier(user.tenant_id, user.id, poId, sendEmail); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); } await loadPurchaseOrders(currentParams); return updatedOrder; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to send order to supplier'; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const cancelOrder = useCallback(async (poId: string, reason: string): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedOrder = await suppliersService.cancelPurchaseOrder(user.tenant_id, user.id, poId, reason); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); } await loadPurchaseOrders(currentParams); return updatedOrder; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to cancel order'; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const clearError = useCallback(() => { setError(null); }, []); const refresh = useCallback(async () => { await loadPurchaseOrders(currentParams); if (statistics) await loadStatistics(); if (ordersRequiringApproval.length > 0) await loadOrdersRequiringApproval(); if (overdueOrders.length > 0) await loadOverdueOrders(); }, [currentParams, statistics, ordersRequiringApproval.length, overdueOrders.length, loadPurchaseOrders, loadStatistics, loadOrdersRequiringApproval, loadOverdueOrders]); const setPage = useCallback((page: number) => { setPagination(prev => ({ ...prev, page })); const offset = (page - 1) * pagination.limit; loadPurchaseOrders({ ...currentParams, offset }); }, [pagination.limit, currentParams, loadPurchaseOrders]); return { purchaseOrders, purchaseOrder, statistics, ordersRequiringApproval, overdueOrders, isLoading, isCreating, error, pagination, loadPurchaseOrders, loadPurchaseOrder, loadStatistics, loadOrdersRequiringApproval, loadOverdueOrders, createPurchaseOrder, updateOrderStatus, approveOrder, sendToSupplier, cancelOrder, clearError, refresh, setPage }; } // ============================================================================ // DELIVERIES HOOK // ============================================================================ export interface UseDeliveries { deliveries: Delivery[]; delivery: Delivery | null; todaysDeliveries: Delivery[]; overdueDeliveries: Delivery[]; performanceStats: DeliveryPerformanceStats | null; isLoading: boolean; error: string | null; pagination: { page: number; limit: number; total: number; totalPages: number; }; loadDeliveries: (params?: DeliverySearchParams) => Promise; loadDelivery: (deliveryId: string) => Promise; loadTodaysDeliveries: () => Promise; loadOverdueDeliveries: () => Promise; loadPerformanceStats: (daysBack?: number, supplierId?: string) => Promise; updateDeliveryStatus: (deliveryId: string, status: string, notes?: string) => Promise; receiveDelivery: (deliveryId: string, receiptData: any) => Promise; clearError: () => void; refresh: () => Promise; setPage: (page: number) => void; } export function useDeliveries(): UseDeliveries { const { user } = useAuth(); // State const [deliveries, setDeliveries] = useState([]); const [delivery, setDelivery] = useState(null); const [todaysDeliveries, setTodaysDeliveries] = useState([]); const [overdueDeliveries, setOverdueDeliveries] = useState([]); const [performanceStats, setPerformanceStats] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [currentParams, setCurrentParams] = useState({}); const [pagination, setPagination] = useState({ page: 1, limit: 50, total: 0, totalPages: 0 }); // Load deliveries const loadDeliveries = useCallback(async (params: DeliverySearchParams = {}) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const searchParams = { ...params, limit: pagination.limit, offset: ((params.offset !== undefined ? Math.floor(params.offset / pagination.limit) : pagination.page) - 1) * pagination.limit }; setCurrentParams(params); const data = await suppliersService.getDeliveries(user.tenant_id, searchParams); setDeliveries(data); // Update pagination const hasMore = data.length === pagination.limit; const currentPage = Math.floor((searchParams.offset || 0) / pagination.limit) + 1; setPagination(prev => ({ ...prev, page: currentPage, total: hasMore ? (currentPage * pagination.limit) + 1 : (currentPage - 1) * pagination.limit + data.length, totalPages: hasMore ? currentPage + 1 : currentPage })); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load deliveries'); } finally { setIsLoading(false); } }, [user?.tenant_id, pagination.limit]); const loadDelivery = useCallback(async (deliveryId: string) => { if (!user?.tenant_id) return; try { setIsLoading(true); setError(null); const data = await suppliersService.getDelivery(user.tenant_id, deliveryId); setDelivery(data); } catch (err: any) { setError(err.response?.data?.detail || err.message || 'Failed to load delivery'); } finally { setIsLoading(false); } }, [user?.tenant_id]); const loadTodaysDeliveries = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getTodaysDeliveries(user.tenant_id); setTodaysDeliveries(data); } catch (err: any) { console.error('Failed to load today\'s deliveries:', err); } }, [user?.tenant_id]); const loadOverdueDeliveries = useCallback(async () => { if (!user?.tenant_id) return; try { const data = await suppliersService.getOverdueDeliveries(user.tenant_id); setOverdueDeliveries(data); } catch (err: any) { console.error('Failed to load overdue deliveries:', err); } }, [user?.tenant_id]); const loadPerformanceStats = useCallback(async (daysBack: number = 30, supplierId?: string) => { if (!user?.tenant_id) return; try { const data = await suppliersService.getDeliveryPerformanceStats(user.tenant_id, daysBack, supplierId); setPerformanceStats(data); } catch (err: any) { console.error('Failed to load delivery performance stats:', err); } }, [user?.tenant_id]); const updateDeliveryStatus = useCallback(async (deliveryId: string, status: string, notes?: string): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedDelivery = await suppliersService.updateDeliveryStatus(user.tenant_id, user.id, deliveryId, status, notes); if (delivery?.id === deliveryId) { setDelivery(updatedDelivery); } await loadDeliveries(currentParams); return updatedDelivery; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to update delivery status'; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, delivery?.id, loadDeliveries, currentParams]); const receiveDelivery = useCallback(async (deliveryId: string, receiptData: any): Promise => { if (!user?.tenant_id || !user?.id) return null; try { setError(null); const updatedDelivery = await suppliersService.receiveDelivery(user.tenant_id, user.id, deliveryId, receiptData); if (delivery?.id === deliveryId) { setDelivery(updatedDelivery); } await loadDeliveries(currentParams); return updatedDelivery; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to receive delivery'; setError(errorMessage); return null; } }, [user?.tenant_id, user?.id, delivery?.id, loadDeliveries, currentParams]); const clearError = useCallback(() => { setError(null); }, []); const refresh = useCallback(async () => { await loadDeliveries(currentParams); if (todaysDeliveries.length > 0) await loadTodaysDeliveries(); if (overdueDeliveries.length > 0) await loadOverdueDeliveries(); if (performanceStats) await loadPerformanceStats(); }, [currentParams, todaysDeliveries.length, overdueDeliveries.length, performanceStats, loadDeliveries, loadTodaysDeliveries, loadOverdueDeliveries, loadPerformanceStats]); const setPage = useCallback((page: number) => { setPagination(prev => ({ ...prev, page })); const offset = (page - 1) * pagination.limit; loadDeliveries({ ...currentParams, offset }); }, [pagination.limit, currentParams, loadDeliveries]); return { deliveries, delivery, todaysDeliveries, overdueDeliveries, performanceStats, isLoading, error, pagination, loadDeliveries, loadDelivery, loadTodaysDeliveries, loadOverdueDeliveries, loadPerformanceStats, updateDeliveryStatus, receiveDelivery, clearError, refresh, setPage }; }