import { useMemo, useState } from 'react'; import { AlertTriangle, ArrowDown, ArrowUp, BarChart3, CheckCircle, Filter, Minus, Target, TrendingDown, TrendingUp, XCircle, } from 'lucide-react'; import { MainLayout } from '@/components/layout/MainLayout.tsx'; import PageHeader from '@/components/layout/PageHeader.tsx'; import Badge from '@/components/ui/Badge.tsx'; import Button from '@/components/ui/Button.tsx'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx'; import { Container, Flex, Grid, Stack } from '@/components/ui/layout'; import Select from '@/components/ui/Select.tsx'; import Spinner from '@/components/ui/Spinner.tsx'; import { useSupplyDemandAnalysis } from '@/hooks/api/useAnalyticsAPI.ts'; import { useTranslation } from '@/hooks/useI18n.tsx'; import { useNavigation } from '@/hooks/useNavigation.tsx'; const SupplyDemandAnalysis = () => { const { t } = useTranslation(); const { handleBackNavigation, handleFooterNavigate } = useNavigation(); const { data: supplyDemandData, isLoading } = useSupplyDemandAnalysis(); const [selectedSector, setSelectedSector] = useState('all'); const [sortBy, setSortBy] = useState('gap'); // Process supply/demand data const analysis = useMemo(() => { const data = supplyDemandData || {}; const topNeeds = (data as any).top_needs || []; const topOffers = (data as any).top_offers || []; const marketGaps = (data as any).market_gaps || []; // Create combined analysis const resourceAnalysis = new Map(); // Process needs topNeeds.forEach((need: any) => { if (!resourceAnalysis.has(need.item)) { resourceAnalysis.set(need.item, { resource: need.item, sector: need.sector, demand: 0, supply: 0, gap: 0, gapPercentage: 0, status: 'unknown', }); } resourceAnalysis.get(need.item).demand = need.count; }); // Process offers topOffers.forEach((offer: any) => { if (!resourceAnalysis.has(offer.item)) { resourceAnalysis.set(offer.item, { resource: offer.item, sector: offer.sector, demand: 0, supply: 0, gap: 0, gapPercentage: 0, status: 'unknown', }); } resourceAnalysis.get(offer.item).supply = offer.count; }); // Calculate gaps and status const analysisArray = Array.from(resourceAnalysis.values()).map((item: any) => { const gap = item.supply - item.demand; const total = item.supply + item.demand; const gapPercentage = total > 0 ? (gap / total) * 100 : 0; let status = 'balanced'; if (gap > 10) status = 'surplus'; else if (gap < -10) status = 'shortage'; return { ...item, gap, gapPercentage: Math.abs(gapPercentage), status, }; }); // Filter by sector const filteredAnalysis = selectedSector === 'all' ? analysisArray : analysisArray.filter((item: any) => item.sector === selectedSector); // Sort const sortedAnalysis = filteredAnalysis.sort((a: any, b: any) => { switch (sortBy) { case 'gap': return Math.abs(b.gap) - Math.abs(a.gap); case 'demand': return b.demand - a.demand; case 'supply': return b.supply - a.supply; case 'resource': return a.resource.localeCompare(b.resource); default: return 0; } }); // Get unique sectors const sectors = Array.from(new Set(analysisArray.map((item: any) => item.sector))); return { analysis: sortedAnalysis, sectors, marketGaps, summary: { totalResources: analysisArray.length, surplusCount: analysisArray.filter((item: any) => item.status === 'surplus').length, shortageCount: analysisArray.filter((item: any) => item.status === 'shortage').length, balancedCount: analysisArray.filter((item: any) => item.status === 'balanced').length, }, }; }, [supplyDemandData, selectedSector, sortBy]); const getStatusIcon = (status: string) => { switch (status) { case 'surplus': return ; case 'shortage': return ; case 'balanced': return ; default: return ; } }; const getStatusColor = (status: string) => { switch (status) { case 'surplus': return 'text-green-700 bg-green-50 border-green-200'; case 'shortage': return 'text-red-700 bg-red-50 border-red-200'; case 'balanced': return 'text-blue-700 bg-blue-50 border-blue-200'; default: return 'text-muted-foreground bg-muted/50 border-muted'; } }; const getStatusBadgeVariant = (status: string) => { switch (status) { case 'surplus': return 'default'; case 'shortage': return 'destructive'; case 'balanced': return 'secondary'; default: return 'outline'; } }; // Simple bar chart for supply vs demand const SupplyDemandBar = ({ supply, demand }: { supply: number; demand: number }) => { const maxValue = Math.max(supply, demand, 1); const supplyPercent = (supply / maxValue) * 100; const demandPercent = (demand / maxValue) * 100; return (
Supply: {supply} Demand: {demand}
); }; const sectorOptions = [ { value: 'all', label: t('supplyDemand.allSectors') }, ...analysis.sectors.map((sector) => ({ value: sector, label: sector })), ]; const sortOptions = [ { value: 'gap', label: t('supplyDemand.sortByGap') }, { value: 'demand', label: t('supplyDemand.sortByDemand') }, { value: 'supply', label: t('supplyDemand.sortBySupply') }, { value: 'resource', label: t('supplyDemand.sortByResource') }, ]; return ( {/* Summary Cards */}

{t('supplyDemand.totalResources')}

{analysis.summary.totalResources}

{t('supplyDemand.surplusResources')}

{analysis.summary.surplusCount}

{t('supplyDemand.shortageResources')}

{analysis.summary.shortageCount}

{t('supplyDemand.balancedResources')}

{analysis.summary.balancedCount}

{/* Filters */} {t('supplyDemand.filters')}
{/* Supply/Demand Analysis Table */} {t('supplyDemand.resourceAnalysis')} {isLoading ? (
) : analysis.analysis.length > 0 ? (
{analysis.analysis.map((item: any, index: number) => (
{getStatusIcon(item.status)}

{item.resource}

{item.status}

{item.sector}

Gap: {item.gap > 0 ? '+' : ''} {item.gap}
{item.gapPercentage.toFixed(1)}% imbalance
Supply: {item.supply} Demand: {item.demand} Gap: {item.gap > 0 ? '+' : ''} {item.gap}
))}
) : (

{t('supplyDemand.noAnalysisData')}

)}
{/* Market Gaps & Opportunities */} {analysis.marketGaps.length > 0 && ( {t('supplyDemand.marketGaps')}
{analysis.marketGaps.map((gap: any, index: number) => (

{gap.resource_type}

{gap.sector}

{gap.description}

{t('supplyDemand.potentialMatches')}: {gap.potential_matches}
{t('supplyDemand.gapSeverity')}: {gap.severity}
))}
)} {/* Recommendations */} {t('supplyDemand.recommendations')} {analysis.summary.shortageCount > 0 && (

{t('supplyDemand.addressShortages')}

{t('supplyDemand.shortageRecommendation')}

)} {analysis.summary.surplusCount > 0 && (

{t('supplyDemand.optimizeSurplus')}

{t('supplyDemand.surplusRecommendation')}

)}

{t('supplyDemand.monitorTrends')}

{t('supplyDemand.monitoringRecommendation')}

); }; export default SupplyDemandAnalysis;