Fix and restyle forecast component

This commit is contained in:
2025-01-17 23:44:13 -05:00
parent 5987b7173d
commit 9759bac94f
4 changed files with 137 additions and 95 deletions

View File

@@ -6,16 +6,24 @@ import config from "@/config"
import { formatCurrency } from "@/lib/utils"
import { TrendingUp, DollarSign } from "lucide-react"
import { DateRange } from "react-day-picker"
import { addDays } from "date-fns"
import { addDays, format } from "date-fns"
import { DateRangePicker } from "@/components/ui/date-range-picker-narrow"
interface ForecastData {
forecastSales: number
forecastRevenue: number
dailyForecast: {
confidenceLevel: number
dailyForecasts: {
date: string
sales: number
units: number
revenue: number
confidence: number
}[]
categoryForecasts: {
category: string
units: number
revenue: number
confidence: number
}[]
}
@@ -25,24 +33,28 @@ export function ForecastMetrics() {
to: addDays(new Date(), 30),
});
const { data } = useQuery<ForecastData>({
const { data, error, isLoading } = useQuery<ForecastData>({
queryKey: ["forecast-metrics", dateRange],
queryFn: async () => {
const params = new URLSearchParams({
startDate: dateRange.from?.toISOString() || "",
endDate: dateRange.to?.toISOString() || "",
});
console.log('Fetching forecast metrics with params:', params.toString());
const response = await fetch(`${config.apiUrl}/dashboard/forecast/metrics?${params}`)
if (!response.ok) {
throw new Error("Failed to fetch forecast metrics")
const text = await response.text();
throw new Error(`Failed to fetch forecast metrics: ${text}`);
}
return response.json()
const data = await response.json();
console.log('Forecast metrics response:', data);
return data;
},
})
return (
<>
<CardHeader className="flex flex-row items-center justify-between pr-4">
<CardHeader className="flex flex-row items-center justify-between pr-5">
<CardTitle className="text-xl font-medium">Forecast</CardTitle>
<div className="w-[230px]">
<DateRangePicker
@@ -54,76 +66,64 @@ export function ForecastMetrics() {
/>
</div>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-4">
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Forecast Sales</p>
<CardContent className="py-0 -mb-2">
{error ? (
<div className="text-sm text-red-500">Error: {error.message}</div>
) : isLoading ? (
<div className="text-sm">Loading forecast metrics...</div>
) : (
<>
<div className="flex flex-col gap-4">
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Forecast Sales</p>
</div>
<p className="text-lg font-bold">{data?.forecastSales.toLocaleString() || 0}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Forecast Revenue</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.forecastRevenue || 0)}</p>
</div>
</div>
<p className="text-2xl font-bold">{data?.forecastSales.toLocaleString() || 0}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Forecast Revenue</p>
</div>
<p className="text-2xl font-bold">{formatCurrency(data?.forecastRevenue || 0)}</p>
</div>
</div>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data?.dailyForecast || []}>
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
fontSize={12}
/>
<YAxis
yAxisId="left"
tickLine={false}
axisLine={false}
fontSize={12}
tickFormatter={(value) => value.toLocaleString()}
/>
<YAxis
yAxisId="right"
orientation="right"
tickLine={false}
axisLine={false}
fontSize={12}
tickFormatter={(value) => formatCurrency(value)}
/>
<Tooltip
formatter={(value: number, name: string) => [
name === "revenue" ? formatCurrency(value) : value.toLocaleString(),
name === "revenue" ? "Revenue" : "Sales"
]}
labelFormatter={(label) => `Date: ${label}`}
/>
<Area
yAxisId="left"
type="monotone"
dataKey="sales"
name="Sales"
stroke="#0088FE"
fill="#0088FE"
fillOpacity={0.2}
/>
<Area
yAxisId="right"
type="monotone"
dataKey="revenue"
name="Revenue"
stroke="#00C49F"
fill="#00C49F"
fillOpacity={0.2}
/>
</AreaChart>
</ResponsiveContainer>
</div>
<div className="h-[250px] w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={data?.dailyForecasts || []}
margin={{ top: 30, right: 0, left: -60, bottom: 0 }}
>
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tick={false}
/>
<YAxis
tickLine={false}
axisLine={false}
tick={false}
/>
<Tooltip
formatter={(value: number) => [formatCurrency(value), "Revenue"]}
labelFormatter={(date) => format(new Date(date), 'MMM d, yyyy')}
/>
<Area
type="monotone"
dataKey="revenue"
name="Revenue"
stroke="#00C49F"
fill="#00C49F"
fillOpacity={0.2}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</>
)}
</CardContent>
</>
)

View File

@@ -33,7 +33,7 @@ export function TopReplenishProducts() {
<CardTitle className="text-xl font-medium">Top Products To Replenish</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="max-h-[650px] w-full overflow-y-auto">
<ScrollArea className="max-h-[530px] w-full overflow-y-auto">
<Table>
<TableHeader>
<TableRow>