Switch to daterangepicker for forecast and sales
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip } from "recharts"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useState } from "react"
|
||||
import config from "@/config"
|
||||
import { formatCurrency } from "@/lib/utils"
|
||||
import { TrendingUp, DollarSign } from "lucide-react" // Importing icons
|
||||
import { TrendingUp, DollarSign } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
import { addDays } from "date-fns"
|
||||
import { DateRangePicker } from "@/components/ui/date-range-picker-narrow"
|
||||
|
||||
interface ForecastData {
|
||||
forecastSales: number
|
||||
@@ -17,21 +19,20 @@ interface ForecastData {
|
||||
}[]
|
||||
}
|
||||
|
||||
const periods = [
|
||||
{ value: "7", label: "7 Days" },
|
||||
{ value: "14", label: "14 Days" },
|
||||
{ value: "30", label: "30 Days" },
|
||||
{ value: "60", label: "60 Days" },
|
||||
{ value: "90", label: "90 Days" },
|
||||
]
|
||||
|
||||
export function ForecastMetrics() {
|
||||
const [period, setPeriod] = useState("30")
|
||||
const [dateRange, setDateRange] = useState<DateRange>({
|
||||
from: new Date(),
|
||||
to: addDays(new Date(), 30),
|
||||
});
|
||||
|
||||
const { data } = useQuery<ForecastData>({
|
||||
queryKey: ["forecast-metrics", period],
|
||||
queryKey: ["forecast-metrics", dateRange],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/forecast/metrics?days=${period}`)
|
||||
const params = new URLSearchParams({
|
||||
startDate: dateRange.from?.toISOString() || "",
|
||||
endDate: dateRange.to?.toISOString() || "",
|
||||
});
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/forecast/metrics?${params}`)
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch forecast metrics")
|
||||
}
|
||||
@@ -41,20 +42,17 @@ export function ForecastMetrics() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardHeader className="flex flex-row items-center justify-between pr-4">
|
||||
<CardTitle className="text-xl font-medium">Forecast</CardTitle>
|
||||
<Select value={period} onValueChange={setPeriod}>
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{periods.map((p) => (
|
||||
<SelectItem key={p.value} value={p.value}>
|
||||
{p.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="w-[230px]">
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onChange={(range) => {
|
||||
if (range) setDateRange(range);
|
||||
}}
|
||||
future={true}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip } from "recharts"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useState } from "react"
|
||||
import config from "@/config"
|
||||
import { formatCurrency } from "@/lib/utils"
|
||||
import { ClipboardList, Package, DollarSign, ShoppingCart } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
import { addDays } from "date-fns"
|
||||
import { DateRangePicker } from "@/components/ui/date-range-picker-narrow"
|
||||
|
||||
interface SalesData {
|
||||
totalOrders: number
|
||||
@@ -20,21 +22,20 @@ interface SalesData {
|
||||
}[]
|
||||
}
|
||||
|
||||
const periods = [
|
||||
{ value: "7", label: "7 Days" },
|
||||
{ value: "14", label: "14 Days" },
|
||||
{ value: "30", label: "30 Days" },
|
||||
{ value: "60", label: "60 Days" },
|
||||
{ value: "90", label: "90 Days" },
|
||||
]
|
||||
|
||||
export function SalesMetrics() {
|
||||
const [period, setPeriod] = useState("30")
|
||||
const [dateRange, setDateRange] = useState<DateRange>({
|
||||
from: addDays(new Date(), -30),
|
||||
to: new Date(),
|
||||
});
|
||||
|
||||
const { data } = useQuery<SalesData>({
|
||||
queryKey: ["sales-metrics", period],
|
||||
queryKey: ["sales-metrics", dateRange],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/sales/metrics?days=${period}`)
|
||||
const params = new URLSearchParams({
|
||||
startDate: dateRange.from?.toISOString() || "",
|
||||
endDate: dateRange.to?.toISOString() || "",
|
||||
});
|
||||
const response = await fetch(`${config.apiUrl}/dashboard/sales/metrics?${params}`)
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch sales metrics")
|
||||
}
|
||||
@@ -44,20 +45,17 @@ export function SalesMetrics() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardHeader className="flex flex-row items-center justify-between pr-4">
|
||||
<CardTitle className="text-xl font-medium">Sales Overview</CardTitle>
|
||||
<Select value={period} onValueChange={setPeriod}>
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{periods.map((p) => (
|
||||
<SelectItem key={p.value} value={p.value}>
|
||||
{p.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="w-[230px]">
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onChange={(range) => {
|
||||
if (range) setDateRange(range);
|
||||
}}
|
||||
future={false}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
135
inventory/src/components/ui/date-range-picker-narrow.tsx
Normal file
135
inventory/src/components/ui/date-range-picker-narrow.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { format, addDays, startOfYear, endOfYear, subDays } from "date-fns";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
interface DateRangePickerProps {
|
||||
value: DateRange;
|
||||
onChange: (range: DateRange | undefined) => void;
|
||||
className?: string;
|
||||
future?: boolean;
|
||||
}
|
||||
|
||||
export function DateRangePicker({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
future = false,
|
||||
}: DateRangePickerProps) {
|
||||
const today = new Date();
|
||||
|
||||
const presets = future ? [
|
||||
{
|
||||
label: "Next 30 days",
|
||||
range: {
|
||||
from: today,
|
||||
to: addDays(today, 30),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Next 90 days",
|
||||
range: {
|
||||
from: today,
|
||||
to: addDays(today, 90),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Rest of year",
|
||||
range: {
|
||||
from: today,
|
||||
to: endOfYear(today),
|
||||
},
|
||||
},
|
||||
] : [
|
||||
{
|
||||
label: "Last 7 days",
|
||||
range: {
|
||||
from: subDays(today, 7),
|
||||
to: today,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Last 30 days",
|
||||
range: {
|
||||
from: subDays(today, 30),
|
||||
to: today,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Last 90 days",
|
||||
range: {
|
||||
from: subDays(today, 90),
|
||||
to: today,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Year to date",
|
||||
range: {
|
||||
from: startOfYear(today),
|
||||
to: today,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-1", className)}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild className="p-3">
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"h-8 w-[230px] justify-start text-left font-normal",
|
||||
!value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
{value?.from ? (
|
||||
value.to ? (
|
||||
<>
|
||||
{format(value.from, "LLL d, y")} -{" "}
|
||||
{format(value.to, "LLL d, y")}
|
||||
</>
|
||||
) : (
|
||||
format(value.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date range</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-3" align="start">
|
||||
<div className="flex gap-2 pb-4">
|
||||
{presets.map((preset) => (
|
||||
<Button
|
||||
key={preset.label}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onChange(preset.range)}
|
||||
>
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={value?.from}
|
||||
selected={value}
|
||||
onSelect={(range) => {
|
||||
if (range) onChange(range);
|
||||
}}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user