From 624bfa54850db4b03e8f523dcfa8772f95cb03bf Mon Sep 17 00:00:00 2001 From: g Date: Mon, 12 Jan 2026 02:32:23 +0000 Subject: [PATCH] balls :D --- components/analytics/PredictionsChart.tsx | 57 ++++++++++++++++++++--- components/analytics/RevenueChart.tsx | 56 +++++++++++++--------- lib/services/analytics-service.ts | 6 ++- 3 files changed, 89 insertions(+), 30 deletions(-) diff --git a/components/analytics/PredictionsChart.tsx b/components/analytics/PredictionsChart.tsx index 6c94f46..142ef57 100644 --- a/components/analytics/PredictionsChart.tsx +++ b/components/analytics/PredictionsChart.tsx @@ -30,6 +30,7 @@ import { Layers, Zap, Info, + Download, } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; @@ -83,13 +84,16 @@ export default function PredictionsChart({ const [daysAhead, setDaysAhead] = useState(7); const [activeTab, setActiveTab] = useState<"overview" | "stock">("overview"); const [simulationFactor, setSimulationFactor] = useState(0); + const [committedSimulationFactor, setCommittedSimulationFactor] = useState(0); const { toast } = useToast(); const fetchPredictions = async () => { try { setLoading(true); + // Convert percentage (e.g. 50) to factor (e.g. 0.5) + const factor = committedSimulationFactor / 100; const [overview, stock] = await Promise.all([ - getPredictionsOverviewWithStore(daysAhead, timeRange), + getPredictionsOverviewWithStore(daysAhead, timeRange, factor), getStockPredictionsWithStore(timeRange), ]); setPredictions(overview); @@ -108,7 +112,7 @@ export default function PredictionsChart({ useEffect(() => { fetchPredictions(); - }, [daysAhead, timeRange]); + }, [daysAhead, timeRange, committedSimulationFactor]); const getConfidenceColor = (confidence: string) => { switch (confidence) { @@ -145,10 +149,41 @@ export default function PredictionsChart({ return predictions.sales.dailyPredictions.map((d) => ({ ...d, formattedDate: format(new Date(d.date), "MMM d"), - value: d.predicted * (1 + simulationFactor / 100), - originalValue: d.predicted, + value: d.predicted, })); - }, [predictions, simulationFactor]); + }, [predictions]); + + const handleExportCSV = () => { + if (!simulatedData.length) return; + + // Create CSV headers + const headers = ["Date", "Predicted Revenue", "Orders", "Confidence"]; + + // Create CSV rows + const rows = simulatedData.map(d => [ + format(new Date(d.date), "yyyy-MM-dd"), + d.predicted.toFixed(2), + d.orders || "", // Provide fallback if orders is undefined + predictions?.sales?.confidence || "unknown" + ]); + + // Combine headers and rows + const csvContent = [ + headers.join(","), + ...rows.map(row => row.join(",")) + ].join("\n"); + + // Create download link + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", `predictions_export_${format(new Date(), "yyyyMMdd")}.csv`); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; if (loading) { return ( @@ -462,8 +497,9 @@ export default function PredictionsChart({ value={[simulationFactor]} min={-50} max={50} - step={5} + step={10} onValueChange={(val) => setSimulationFactor(val[0])} + onValueCommit={(val) => setCommittedSimulationFactor(val[0])} className="w-[150px] mt-1.5" /> @@ -472,13 +508,20 @@ export default function PredictionsChart({ variant="ghost" size="icon" className="h-6 w-6" - onClick={() => setSimulationFactor(0)} + onClick={() => { + setSimulationFactor(0); + setCommittedSimulationFactor(0); + }} title="Reset simulation" > )} +
diff --git a/components/analytics/RevenueChart.tsx b/components/analytics/RevenueChart.tsx index 3757076..a272606 100644 --- a/components/analytics/RevenueChart.tsx +++ b/components/analytics/RevenueChart.tsx @@ -7,7 +7,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { TrendingUp, DollarSign } from "lucide-react"; import { getRevenueTrendsWithStore, type RevenueData } from "@/lib/services/analytics-service"; import { formatGBP } from "@/utils/format"; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from 'recharts'; import { ChartSkeleton } from './SkeletonLoaders'; interface RevenueChartProps { @@ -61,9 +61,9 @@ export default function RevenueChart({ timeRange, hideNumbers = false }: Revenue date: date.toISOString().split('T')[0], // YYYY-MM-DD format revenue: item.revenue || 0, orders: item.orders || 0, - formattedDate: date.toLocaleDateString('en-GB', { + formattedDate: date.toLocaleDateString('en-GB', { weekday: 'short', - month: 'short', + month: 'short', day: 'numeric', timeZone: 'UTC' }) @@ -79,12 +79,12 @@ export default function RevenueChart({ timeRange, hideNumbers = false }: Revenue // Function to mask sensitive numbers const maskValue = (value: string): string => { if (!hideNumbers) return value; - + // For currency values (£X.XX), show £*** if (value.includes('£')) { return '£***'; } - + // For regular numbers, replace with asterisks return '***'; }; @@ -110,7 +110,7 @@ export default function RevenueChart({ timeRange, hideNumbers = false }: Revenue if (isLoading) { return ( - - - - + + + + + + + + - hideNumbers ? '***' : `£${(value / 1000).toFixed(0)}k`} /> - } /> - } cursor={{ fill: "transparent", stroke: "hsl(var(--muted-foreground))", strokeDasharray: "3 3" }} /> + - +
- + {/* Summary stats */}
diff --git a/lib/services/analytics-service.ts b/lib/services/analytics-service.ts index 8dace69..9bd1513 100644 --- a/lib/services/analytics-service.ts +++ b/lib/services/analytics-service.ts @@ -494,15 +494,18 @@ export const getStockPredictions = async ( * @param daysAhead Number of days to predict ahead (default: 7) * @param period Historical period in days (default: 30) * @param storeId Optional storeId for staff users + * @param simulation Simulation factor (e.g. 0.1 for +10%) */ export const getPredictionsOverview = async ( daysAhead: number = 7, period: number = 30, storeId?: string, + simulation: number = 0, ): Promise => { const params = new URLSearchParams({ daysAhead: daysAhead.toString(), period: period.toString(), + simulation: simulation.toString(), }); if (storeId) params.append("storeId", storeId); @@ -538,7 +541,8 @@ export const getStockPredictionsWithStore = async ( export const getPredictionsOverviewWithStore = async ( daysAhead: number = 7, period: number = 30, + simulation: number = 0, ): Promise => { const storeId = getStoreIdForUser(); - return getPredictionsOverview(daysAhead, period, storeId); + return getPredictionsOverview(daysAhead, period, storeId, simulation); };