1import { u as useDealership, r as reactExports, t as trpc, j as jsxRuntimeExports, T as TrendingUp, I as Calendar, f as Badge } from "./index-DZUVA1tZ.js"; 2import { P as PageHeader } from "./PageHeader-BeAccEfP.js"; 3import { C as Card, c as CardContent, a as CardHeader, b as CardTitle } from "./card-C2_jI6O8.js"; 4import { S as Skeleton } from "./skeleton-BpqpJ9I-.js"; 5import { C as ChartContainer, a as ChartTooltip, b as ChartTooltipContent } from "./chart-DqrXm1Bo.js"; 6import { A as ArrowUpRight } from "./arrow-up-right-CbhoZhFr.js"; 7import { A as ArrowDownRight } from "./arrow-down-right-BcLpnoyf.js"; 8import { A as AreaChart } from "./AreaChart-DyJHC5E8.js"; 9import { C as CartesianGrid } from "./CartesianGrid-Dju2DZc9.js"; 10import { X as XAxis, Y as YAxis } from "./YAxis-vcBlqPnO.js"; 11import { F as ReferenceLine } from "./generateCategoricalChart-DS6b7n0z.js"; 12import { A as Area } from "./Area-B_6roka7.js"; 13import "./calendar-B8_9t0D4.js"; 14import "./format-B8OmckKX.js"; 15import "./endOfMonth-BBZWhvnk.js"; 16import "./chevron-left-BUMmw3WR.js"; 17import "./calendar-days-kCkFTmM5.js"; 18import "./download-2Lv-z89u.js"; 19import "./string-Cl2mTqJp.js"; 20import "./band-CR10Xg6M.js"; 21import "./init-CTO7spbL.js"; 22import "./ordinal-B3yiDjK9.js"; 23import "./linear-CiVNRwv0.js"; 24import "./defaultLocale-DVr69WTU.js"; 25import "./time-ByDv7dtv.js"; 26function fmt$(v) { 27 if (Math.abs(v) >= 1e6) return `$${(v / 1e6).toFixed(1)}M`; 28 if (Math.abs(v) >= 1e3) return `$${(v / 1e3).toFixed(0)}K`; 29 return `$${v.toLocaleString()}`; 30} 31function SalesForecast() { 32 const { selectedDealerId } = useDealership(); 33 const [month] = reactExports.useState(() => { 34 const d = /* @__PURE__ */ new Date(); 35 return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; 36 }); 37 const { data, isLoading } = trpc.deals.forecast.useQuery( 38 { dealerId: selectedDealerId }, 39 { enabled: !!selectedDealerId } 40 ); 41 const chartData = reactExports.useMemo(() => { 42 if (!data?.dailyProjection) return []; 43 return data.dailyProjection; 44 }, [data]); 45 if (isLoading) { 46 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 space-y-6 p-4 sm:p-6", children: [
47 /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader, { title: "Sales Forecast", description: "AI-powered sales projections" }), 48 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4 sm:gap-4", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-28 rounded-xl" }, i)) }), 49 /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-80 rounded-xl" }) 50 ] }); 51 } 52 const forecast = data; 53 const projectedUnits = forecast?.projectedUnits ?? 0; 54 const projectedGross = forecast?.projectedGross ?? 0; 55 const confidence = forecast?.confidence ?? 0; 56 const trend = forecast?.trend ?? "flat"; 57 const lastMonthUnits = forecast?.lastMonthUnits ?? 0; 58 const unitsDelta = projectedUnits - lastMonthUnits; 59 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 space-y-6 p-4 sm:p-6", children: [ 60 /* @__PURE__ */ jsxRuntimeExports.jsx( 61 PageHeader, 62 { 63 title: "Sales Forecast", 64 description: "AI-powered projections based on current pace, historical patterns, and pipeline strength" 65 } 66 ), 67 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4", children: [
68 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { className: "min-w-0 border-border/50 bg-gradient-to-br from-primary/5 to-transparent", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "min-w-0 p-3 sm:p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-col items-start gap-1 sm:flex-row sm:items-center sm:justify-between", children: [ 69 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [ 70 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Projected Units" }), 71 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "break-words text-2xl font-bold tabular-nums sm:text-3xl", children: projectedUnits }) 72 ] }), 73 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `shrink-0 flex items-center gap-1 text-xs font-medium sm:text-sm ${unitsDelta >= 0 ? "text-emerald-400" : "text-red-400"}`, children: [ 74 unitsDelta >= 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowUpRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownRight, { className: "size-4" }), 75 unitsDelta >= 0 ? "+" : "", 76 unitsDelta, 77 " vs LM" 78 ] }) 79 ] }) }) }),
80 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { className: "min-w-0 border-border/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "min-w-0 p-3 sm:p-4", children: [ 81 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Projected Gross" }), 82 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "break-words text-2xl font-bold tabular-nums sm:text-3xl", children: fmt$(projectedGross) }), 83 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mt-1", children: "End of month estimate" }) 84 ] }) }), 85 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { className: "min-w-0 border-border/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "min-w-0 p-3 sm:p-4", children: [ 86 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Confidence" }), 87 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "break-words text-2xl font-bold tabular-nums sm:text-3xl", children: [ 88 confidence, 89 "%" 90 ] }),
91 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1 mt-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 flex-1 rounded-full bg-muted overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 92 "div", 93 { 94 className: "h-full rounded-full transition-all", 95 style: { 96 width: `${confidence}%`, 97 backgroundColor: confidence >= 80 ? "oklch(0.75 0.18 155)" : confidence >= 60 ? "oklch(0.75 0.15 80)" : "oklch(0.65 0.2 25)" 98 } 99 } 100 ) }) }) 101 ] }) }), 102 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { className: "min-w-0 border-border/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "min-w-0 p-3 sm:p-4", children: [ 103 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Trend" }), 104 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
105 /* @__PURE__ */ jsxRuntimeExports.jsx(TrendingUp, { className: `size-6 ${trend === "up" ? "text-emerald-400" : trend === "down" ? "text-red-400 rotate-180" : "text-muted-foreground"}` }), 106 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl font-bold capitalize", children: trend }) 107 ] }), 108 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground mt-1", children: "Based on 3-month pattern" }) 109 ] }) }) 110 ] }), 111 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "min-w-0 border-border/50", children: [ 112 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex min-w-0 flex-wrap items-center gap-2 text-sm font-semibold", children: [ 113 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "size-4 text-primary" }), 114 "Daily Cumulative Projection", 115 /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "ml-auto shrink-0 text-[10px]", children: month }) 116 ] }) }),
117 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: chartData.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChartContainer, { config: {}, className: "h-[240px] w-full sm:h-[300px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(AreaChart, { data: chartData, margin: { top: 10, right: 8, left: -18, bottom: 0 }, children: [ 118 /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [ 119 /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "colorActual", x1: "0", y1: "0", x2: "0", y2: "1", children: [ 120 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "5%", stopColor: "oklch(0.7 0.15 250)", stopOpacity: 0.3 }), 121 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "95%", stopColor: "oklch(0.7 0.15 250)", stopOpacity: 0 }) 122 ] }), 123 /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "colorProjected", x1: "0", y1: "0", x2: "0", y2: "1", children: [ 124 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "5%", stopColor: "oklch(0.75 0.18 155)", stopOpacity: 0.2 }),
125 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "95%", stopColor: "oklch(0.75 0.18 155)", stopOpacity: 0 }) 126 ] }) 127 ] }), 128 /* @__PURE__ */ jsxRuntimeExports.jsx(CartesianGrid, { strokeDasharray: "3 3", className: "stroke-border/30" }), 129 /* @__PURE__ */ jsxRuntimeExports.jsx(XAxis, { dataKey: "day", className: "text-xs", tick: { fontSize: 10 }, tickMargin: 6 }), 130 /* @__PURE__ */ jsxRuntimeExports.jsx(YAxis, { className: "text-xs", tick: { fontSize: 10 }, width: 30 }), 131 /* @__PURE__ */ jsxRuntimeExports.jsx(ChartTooltip, { content: /* @__PURE__ */ jsxRuntimeExports.jsx(ChartTooltipContent, {}) }), 132 forecast?.goal && /* @__PURE__ */ jsxRuntimeExports.jsx(ReferenceLine, { y: forecast.goal, stroke: "oklch(0.65 0.2 25)", strokeDasharray: "5 5", label: { value: `Goal: ${forecast.goal}`, position: "right", className: "text-xs fill-muted-foreground" } }), 133 /* @__PURE__ */ jsxRuntimeExports.jsx(Area, { type: "monotone", dataKey: "actual", stroke: "oklch(0.7 0.15 250)", fill: "url(#colorActual)", strokeWidth: 2, name: "Actual", dot: false }),
134 /* @__PURE__ */ jsxRuntimeExports.jsx(Area, { type: "monotone", dataKey: "projected", stroke: "oklch(0.75 0.18 155)", fill: "url(#colorProjected)", strokeWidth: 2, strokeDasharray: "5 5", name: "Projected", dot: false }), 135 /* @__PURE__ */ jsxRuntimeExports.jsx(Area, { type: "monotone", dataKey: "upperBound", stroke: "none", fill: "oklch(0.75 0.18 155)", fillOpacity: 0.05, name: "Upper Bound", dot: false }), 136 /* @__PURE__ */ jsxRuntimeExports.jsx(Area, { type: "monotone", dataKey: "lowerBound", stroke: "none", fill: "oklch(0.75 0.18 155)", fillOpacity: 0.05, name: "Lower Bound", dot: false }) 137 ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-16 text-muted-foreground", children: "No forecast data available yet." }) }) 138 ] }), 139 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4", children: [ 140 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "min-w-0 border-border/50", children: [ 141 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-sm font-semibold", children: "Forecast Factors" }) }),
142 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 143 (forecast?.factors ?? []).map((f, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-start gap-3 sm:items-center", children: [ 144 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `size-2 rounded-full ${f.impact === "positive" ? "bg-emerald-500" : f.impact === "negative" ? "bg-red-500" : "bg-muted-foreground"}` }), 145 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm", children: f.description }) }), 146 /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: `shrink-0 self-end text-[10px] sm:self-auto ${f.impact === "positive" ? "border-emerald-500/30 text-emerald-400" : f.impact === "negative" ? "border-red-500/30 text-red-400" : ""}`, children: f.weight }) 147 ] }, i)), 148 (!forecast?.factors || forecast.factors.length === 0) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-6 text-muted-foreground text-sm", children: "Factors will appear as more data is collected." }) 149 ] }) }) 150 ] }),
151 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "min-w-0 border-border/50", children: [ 152 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-sm font-semibold", children: "Historical Accuracy" }) }), 153 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 154 (forecast?.history ?? []).map((h, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid min-w-0 grid-cols-[4rem_minmax(0,1fr)_auto] items-center gap-2 sm:gap-3", children: [ 155 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground", children: h.month }), 156 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [ 157 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm tabular-nums", children: h.predicted }),
158 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowUpRight, { className: "size-3 text-muted-foreground" }), 159 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium tabular-nums", children: h.actual }) 160 ] }), 161 /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: `shrink-0 text-[10px] ${Math.abs(h.predicted - h.actual) <= 3 ? "border-emerald-500/30 text-emerald-400" : "border-amber-500/30 text-amber-400"}`, children: [ 162 h.accuracy, 163 "%" 164 ] }) 165 ] }, i)), 166 (!forecast?.history || forecast.history.length === 0) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-6 text-muted-foreground text-sm", children: "Accuracy tracking will appear after the first month." }) 167 ] }) }) 168 ] }) 169 ] }) 170 ] }); 171} 172export { 173 SalesForecast as default 174};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.