Files
bakery-ia/frontend/src/hooks/usePOIContext.ts

210 lines
5.9 KiB
TypeScript

/**
* POI Context React Hook
*
* Custom hook for managing POI context state and operations
*/
import { useState, useEffect, useCallback } from 'react';
import { poiContextApi } from '@/services/api/poiContextApi';
import type {
POIContext,
POIDetectionResponse,
FeatureImportanceResponse,
CompetitorAnalysis
} from '@/types/poi';
export interface UsePOIContextOptions {
tenantId: string;
autoFetch?: boolean;
}
export interface UsePOIContextResult {
poiContext: POIContext | null;
isLoading: boolean;
isRefreshing: boolean;
error: string | null;
isStale: boolean;
needsRefresh: boolean;
featureImportance: FeatureImportanceResponse | null;
competitorAnalysis: CompetitorAnalysis | null;
competitiveInsights: string[];
// Actions
detectPOIs: (latitude: number, longitude: number, forceRefresh?: boolean) => Promise<void>;
refreshPOIs: () => Promise<void>;
fetchContext: () => Promise<void>;
fetchFeatureImportance: () => Promise<void>;
fetchCompetitorAnalysis: () => Promise<void>;
deletePOIContext: () => Promise<void>;
}
export function usePOIContext({ tenantId, autoFetch = true }: UsePOIContextOptions): UsePOIContextResult {
const [poiContext, setPOIContext] = useState<POIContext | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isStale, setIsStale] = useState(false);
const [needsRefresh, setNeedsRefresh] = useState(false);
const [featureImportance, setFeatureImportance] = useState<FeatureImportanceResponse | null>(null);
const [competitorAnalysis, setCompetitorAnalysis] = useState<CompetitorAnalysis | null>(null);
const [competitiveInsights, setCompetitiveInsights] = useState<string[]>([]);
const fetchContext = useCallback(async () => {
if (!tenantId) return;
try {
setIsLoading(true);
setError(null);
const response = await poiContextApi.getPOIContext(tenantId);
setPOIContext(response.poi_context);
setIsStale(response.is_stale);
setNeedsRefresh(response.needs_refresh);
} catch (err: any) {
if (err.response?.status === 404) {
// No POI context found - this is normal for new tenants
setPOIContext(null);
setError(null);
} else {
setError(err.message || 'Failed to fetch POI context');
console.error('Error fetching POI context:', err);
}
} finally {
setIsLoading(false);
}
}, [tenantId]);
const detectPOIs = useCallback(async (
latitude: number,
longitude: number,
forceRefresh: boolean = false
) => {
if (!tenantId) return;
try {
setIsLoading(true);
setError(null);
const response = await poiContextApi.detectPOIs(
tenantId,
latitude,
longitude,
forceRefresh
);
setPOIContext(response.poi_context);
setIsStale(false);
setNeedsRefresh(false);
// Update competitor analysis if available
if (response.competitor_analysis) {
setCompetitorAnalysis(response.competitor_analysis);
}
// Update competitive insights if available
if (response.competitive_insights) {
setCompetitiveInsights(response.competitive_insights);
}
} catch (err: any) {
setError(err.message || 'Failed to detect POIs');
console.error('Error detecting POIs:', err);
} finally {
setIsLoading(false);
}
}, [tenantId]);
const refreshPOIs = useCallback(async () => {
if (!tenantId) return;
try {
setIsRefreshing(true);
setError(null);
const response = await poiContextApi.refreshPOIContext(tenantId);
setPOIContext(response.poi_context);
setIsStale(false);
setNeedsRefresh(false);
// Update competitor analysis if available
if (response.competitor_analysis) {
setCompetitorAnalysis(response.competitor_analysis);
}
// Update competitive insights if available
if (response.competitive_insights) {
setCompetitiveInsights(response.competitive_insights);
}
} catch (err: any) {
setError(err.message || 'Failed to refresh POI context');
console.error('Error refreshing POI context:', err);
} finally {
setIsRefreshing(false);
}
}, [tenantId]);
const fetchFeatureImportance = useCallback(async () => {
if (!tenantId) return;
try {
const response = await poiContextApi.getFeatureImportance(tenantId);
setFeatureImportance(response);
} catch (err: any) {
console.error('Error fetching feature importance:', err);
}
}, [tenantId]);
const fetchCompetitorAnalysis = useCallback(async () => {
if (!tenantId) return;
try {
const response = await poiContextApi.getCompetitorAnalysis(tenantId);
setCompetitorAnalysis(response.competitor_analysis);
setCompetitiveInsights(response.insights);
} catch (err: any) {
console.error('Error fetching competitor analysis:', err);
}
}, [tenantId]);
const deletePOIContext = useCallback(async () => {
if (!tenantId) return;
try {
await poiContextApi.deletePOIContext(tenantId);
setPOIContext(null);
setFeatureImportance(null);
setCompetitorAnalysis(null);
setCompetitiveInsights([]);
setIsStale(false);
setNeedsRefresh(false);
} catch (err: any) {
setError(err.message || 'Failed to delete POI context');
console.error('Error deleting POI context:', err);
}
}, [tenantId]);
// Auto-fetch on mount if enabled
useEffect(() => {
if (autoFetch && tenantId) {
fetchContext();
}
}, [autoFetch, tenantId, fetchContext]);
return {
poiContext,
isLoading,
isRefreshing,
error,
isStale,
needsRefresh,
featureImportance,
competitorAnalysis,
competitiveInsights,
detectPOIs,
refreshPOIs,
fetchContext,
fetchFeatureImportance,
fetchCompetitorAnalysis,
deletePOIContext
};
}