fix chart issues
All checks were successful
Build Frontend / build (push) Successful in 1m10s

This commit is contained in:
g
2026-01-12 04:16:04 +00:00
parent 5d9f8fa07b
commit 6eeed4267a
2 changed files with 112 additions and 31 deletions

View File

@@ -79,24 +79,28 @@ export default function PredictionsChart({
const [predictions, setPredictions] = useState<PredictionsOverview | null>( const [predictions, setPredictions] = useState<PredictionsOverview | null>(
null, null,
); );
const [baselinePredictions, setBaselinePredictions] = useState<PredictionsOverview | null>(
null,
);
const [stockPredictions, setStockPredictions] = const [stockPredictions, setStockPredictions] =
useState<StockPredictionsResponse | null>(null); useState<StockPredictionsResponse | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isSimulating, setIsSimulating] = useState(false);
const [daysAhead, setDaysAhead] = useState(7); const [daysAhead, setDaysAhead] = useState(7);
const [activeTab, setActiveTab] = useState<"overview" | "stock">("overview"); const [activeTab, setActiveTab] = useState<"overview" | "stock">("overview");
const [simulationFactor, setSimulationFactor] = useState(0); const [simulationFactor, setSimulationFactor] = useState(0);
const [committedSimulationFactor, setCommittedSimulationFactor] = useState(0); const [committedSimulationFactor, setCommittedSimulationFactor] = useState(0);
const { toast } = useToast(); const { toast } = useToast();
const fetchPredictions = async () => { // Fetch baseline predictions (simulation factor = 0)
const fetchBaseline = async () => {
try { try {
setLoading(true); setLoading(true);
// Convert percentage (e.g. 50) to factor (e.g. 0.5)
const factor = committedSimulationFactor / 100;
const [overview, stock] = await Promise.all([ const [overview, stock] = await Promise.all([
getPredictionsOverviewWithStore(daysAhead, timeRange, factor), getPredictionsOverviewWithStore(daysAhead, timeRange, 0),
getStockPredictionsWithStore(timeRange), getStockPredictionsWithStore(timeRange),
]); ]);
setBaselinePredictions(overview);
setPredictions(overview); setPredictions(overview);
setStockPredictions(stock); setStockPredictions(stock);
} catch (error) { } catch (error) {
@@ -111,9 +115,43 @@ export default function PredictionsChart({
} }
}; };
// Fetch simulated predictions (without full reload)
const fetchSimulation = async (factor: number) => {
if (factor === 0) {
// Use cached baseline
setPredictions(baselinePredictions);
return;
}
try {
setIsSimulating(true);
const overview = await getPredictionsOverviewWithStore(daysAhead, timeRange, factor / 100);
setPredictions(overview);
} catch (error) {
console.error("Error fetching simulation:", error);
toast({
title: "Error",
description: "Failed to load simulation",
variant: "destructive",
});
} finally {
setIsSimulating(false);
}
};
// Fetch baseline on initial load or when daysAhead/timeRange changes
useEffect(() => { useEffect(() => {
fetchPredictions(); fetchBaseline();
}, [daysAhead, timeRange, committedSimulationFactor]); setCommittedSimulationFactor(0);
setSimulationFactor(0);
}, [daysAhead, timeRange]);
// Fetch simulation when committed slider value changes
useEffect(() => {
if (baselinePredictions) {
fetchSimulation(committedSimulationFactor);
}
}, [committedSimulationFactor]);
const getConfidenceColor = (confidence: string) => { const getConfidenceColor = (confidence: string) => {
switch (confidence) { switch (confidence) {
@@ -145,27 +183,37 @@ export default function PredictionsChart({
} }
}; };
const simulatedData = useMemo(() => { // Combine baseline and simulated data for overlay chart
const chartData = useMemo(() => {
if (!predictions?.sales?.dailyPredictions) return []; if (!predictions?.sales?.dailyPredictions) return [];
return predictions.sales.dailyPredictions.map((d: any) => ({
const baselineData = baselinePredictions?.sales?.dailyPredictions || [];
const simulatedDailyData = predictions.sales.dailyPredictions;
return simulatedDailyData.map((d: any, idx: number) => ({
...d, ...d,
formattedDate: format(new Date(d.date), "MMM d"), formattedDate: format(new Date(d.date), "MMM d"),
value: d.predicted, simulated: d.predicted,
orders: d.predictedOrders || 0, // Ensure orders exist baseline: baselineData[idx]?.predicted ?? d.predicted,
orders: d.predictedOrders || 0,
})); }));
}, [predictions]); }, [predictions, baselinePredictions]);
// Keep simulatedData for export compatibility
const simulatedData = chartData;
const handleExportCSV = () => { const handleExportCSV = () => {
if (!simulatedData.length) return; if (!simulatedData.length) return;
// Create CSV headers // Create CSV headers
const headers = ["Date", "Predicted Revenue", "Orders", "Confidence"]; const headers = ["Date", "Baseline Revenue", "Simulated Revenue", "Orders", "Confidence"];
// Create CSV rows // Create CSV rows
const rows = simulatedData.map(d => [ const rows = simulatedData.map(d => [
format(new Date(d.date), "yyyy-MM-dd"), format(new Date(d.date), "yyyy-MM-dd"),
d.predicted.toFixed(2), d.baseline?.toFixed(2) || "",
d.orders || "", // Provide fallback if orders is undefined d.simulated?.toFixed(2) || "",
d.orders || "",
predictions?.sales?.confidence || "unknown" predictions?.sales?.confidence || "unknown"
]); ]);
@@ -257,11 +305,11 @@ export default function PredictionsChart({
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={fetchPredictions} onClick={fetchBaseline}
disabled={loading} disabled={loading || isSimulating}
> >
<RefreshCw <RefreshCw
className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} className={`h-4 w-4 ${loading || isSimulating ? "animate-spin" : ""}`}
/> />
</Button> </Button>
</div> </div>
@@ -532,10 +580,15 @@ export default function PredictionsChart({
</Button> </Button>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="h-[300px] w-full mt-4"> <div className="h-[300px] w-full mt-4 relative">
{isSimulating && (
<div className="absolute inset-0 flex items-center justify-center bg-background/50 z-10">
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)}
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<AreaChart <AreaChart
data={simulatedData} data={chartData}
margin={{ margin={{
top: 5, top: 5,
right: 10, right: 10,
@@ -544,15 +597,27 @@ export default function PredictionsChart({
}} }}
> >
<defs> <defs>
<linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="colorBaseline" x1="0" y1="0" x2="0" y2="1">
<stop <stop
offset="5%" offset="5%"
stopColor={simulationFactor !== 0 ? "#10b981" : "#8884d8"} stopColor="#8884d8"
stopOpacity={0.6}
/>
<stop
offset="95%"
stopColor="#8884d8"
stopOpacity={0}
/>
</linearGradient>
<linearGradient id="colorSimulated" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="#10b981"
stopOpacity={0.8} stopOpacity={0.8}
/> />
<stop <stop
offset="95%" offset="95%"
stopColor={simulationFactor !== 0 ? "#10b981" : "#8884d8"} stopColor="#10b981"
stopOpacity={0} stopOpacity={0}
/> />
</linearGradient> </linearGradient>
@@ -578,16 +643,32 @@ export default function PredictionsChart({
borderColor: "hsl(var(--border))", borderColor: "hsl(var(--border))",
borderRadius: "var(--radius)", borderRadius: "var(--radius)",
}} }}
formatter={(value: number) => [formatGBP(value), "Revenue"]} formatter={(value: number, name: string) => [
formatGBP(value),
name === "baseline" ? "Baseline" : "Simulated"
]}
/> />
{/* Always show baseline as solid line */}
<Area <Area
type="monotone" type="monotone"
dataKey="value" dataKey="baseline"
stroke={simulationFactor !== 0 ? "#10b981" : "#8884d8"} stroke="#8884d8"
fillOpacity={1} fillOpacity={committedSimulationFactor !== 0 ? 0.3 : 1}
fill="url(#colorValue)" fill="url(#colorBaseline)"
strokeDasharray={simulationFactor !== 0 ? "5 5" : "0"} strokeWidth={committedSimulationFactor !== 0 ? 1 : 2}
/> />
{/* Show simulated line when simulation is active */}
{committedSimulationFactor !== 0 && (
<Area
type="monotone"
dataKey="simulated"
stroke="#10b981"
fillOpacity={0.6}
fill="url(#colorSimulated)"
strokeDasharray="5 5"
strokeWidth={2}
/>
)}
</AreaChart> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>

View File

@@ -1,4 +1,4 @@
{ {
"commitHash": "02ba4b0", "commitHash": "5d9f8fa",
"buildTime": "2026-01-12T03:57:23.436Z" "buildTime": "2026-01-12T04:07:03.023Z"
} }