PageSourceSearch

https://lottrax.com/assets/SalesForecast-XOmZH-Ws.js

js lottrax.com collected 2026-10-09 22:56:15 UTC 14,821 bytes, 174 lines download raw bytes

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.