import { useState, useEffect } from 'react'; import { stockHistoryApi, StockStatusHistoryEntry, StockStatusStats } from '../api/client'; interface StockTimelineProps { productId: number; days?: number; } export default function StockTimeline({ productId, days = 30 }: StockTimelineProps) { const [history, setHistory] = useState([]); const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setIsLoading(true); const response = await stockHistoryApi.getHistory(productId, days); setHistory(response.data.history); setStats(response.data.stats); setError(null); } catch { setError('Failed to load stock history'); } finally { setIsLoading(false); } }; fetchData(); }, [productId, days]); if (isLoading) { return (
); } if (error || !stats || history.length === 0) { return null; // Don't show anything if no stock history data } // Calculate timeline segments const now = new Date(); const periodStart = new Date(now.getTime() - days * 24 * 60 * 60 * 1000); const totalMs = now.getTime() - periodStart.getTime(); const segments: { status: string; startPercent: number; widthPercent: number }[] = []; for (let i = 0; i < history.length; i++) { const entry = history[i]; const entryTime = new Date(entry.changed_at); const nextEntry = history[i + 1]; const nextTime = nextEntry ? new Date(nextEntry.changed_at) : now; // Clip to our period const segmentStart = entryTime < periodStart ? periodStart : entryTime; const segmentEnd = nextTime; if (segmentEnd <= periodStart) continue; const startPercent = ((segmentStart.getTime() - periodStart.getTime()) / totalMs) * 100; const widthPercent = ((segmentEnd.getTime() - segmentStart.getTime()) / totalMs) * 100; segments.push({ status: entry.status, startPercent: Math.max(0, startPercent), widthPercent: Math.min(100 - startPercent, widthPercent), }); } return ( <>
📊

Stock Availability

Last {days} days
{segments.map((segment, index) => (
))}
In Stock
Out of Stock
= 80 ? 'good' : stats.availability_percent >= 50 ? 'neutral' : 'bad'}`}> {stats.availability_percent}%
Availability
{stats.outage_count}
Times Out of Stock
{stats.avg_outage_days !== null && (
{stats.avg_outage_days}d
Avg Outage
)} {stats.longest_outage_days !== null && (
{stats.longest_outage_days}d
Longest Outage
)}
{stats.days_in_current_status}d
{stats.current_status === 'in_stock' ? 'In Stock' : 'Out of Stock'}
); }