Sales Overview
The home analytics page of a store admin. Handles stores with no sales yet, today’s unfinished numbers and reports that fail or come back incomplete.
Sales Overview is the page a merchant opens first: six KPI cards, a sales chart, a live view and three breakdowns, under a date range picker with a compare-to control. You pass loaders that take a query and return data, and the block owns loading, refreshing, errors and retries for each card on its own.
Every KPI card shows its value, a change chip and the period it’s compared with, like “vs Aug 23–Sep 21”. The chip colors by whether up is good for that metric, reads New when the previous period was zero instead of a meaningless ∞%, and shows a dash when there is nothing to compare. When the range changes, only the digits that changed roll; nothing counts up on first load.
Today is never a finished number. Its point on the chart and every sparkline is drawn dotted, the tooltip says “so far”, and the Today preset compares with yesterday at the same time instead of the whole day. Hovering any chart moves one crosshair across all six sparklines and the main chart at once, and the card captions show the hovered day’s values.
The chart picks hours for a single day, days up to two months and weeks beyond that. It can switch between sales, orders and average order value, and View as table shows the same numbers as an accessible table. An empty store shows zeros with a Share your store button, a failed report shows its error with Try again in its own card, and a metric your source can’t provide says so rather than showing a zero.
HextaUI Pro holen
Eine einmalige Zahlung schaltet jeden Block frei, auch künftige. Preise ansehen.
Die Pro-Registry zu components.json hinzufügen
components.json Token hinzufügen
Erstelle auf deiner Kontoseite einen Token und trage ihn in
.env.localalsHEXTAUI_PRO_TOKENein.Den Block hinzufügen
pnpm dlx shadcn@latest add @hextaui-pro/sales-overview
From Shopify analytics
Run one ShopifyQL query per range through the Admin API, grouped by the granularity the query asks for, and map each row to a SalesPoint. Sessions and returning customers come from the same query, so every card fills from one request.
From Stripe charges
List succeeded charges for the range and let bucketize sum them into hours, days or weeks in the shop’s time zone. Stripe has no sessions, so those cards show Not available instead of a fake zero.
Your own SQL endpoint
Point the loaders at a route that runs one GROUP BY per range. The query carries the start, end, comparison range and granularity, and the abort signal cancels a request when the merchant picks another range.
Aufbau
Die Teile, die du zusammensetzt, von außen nach innen.
| Part | Beschreibung |
|---|---|
SalesOverview | The whole page: header, range picker, KPI grid, chart, live view and breakdowns, with data loading built in. |
DateRangePicker | Presets, a range calendar for custom dates and the compare-to select. |
KpiCard | One metric with an info tooltip, rolling value, change chip, comparison caption and sparkline. |
DeltaChip | The change between two periods, colored by whether up is good. |
Sparkline | A small line with the comparison dashed and today dotted, synced to the shared crosshair. |
SalesChart | The main chart with a metric switcher, comparison line, crosshair tooltip and table view. |
LiveView | Visitors right now, orders and sales today and recent orders sliding in. |
ChannelBreakdown, TopProducts, DeviceBreakdown | Breakdown cards with their own loading, empty and error states. |
SalesHoverProvider | Shares the hovered point between charts. SalesOverview adds it for you. |
presetRange, comparisonRange, granularityFor, buildQuery, bucketize, getDelta | Pure helpers for ranges, comparison periods, buckets and percent change. |
SalesOverview
Also takes div props. Dates are shop-local days like 2026-10-21.
| Prop | Typ | Standard |
|---|---|---|
loadersEach is (query, signal) => Promise. series returns { current, previous?, unavailable? } as SalesPoint[]. Leave out a breakdown loader to hide its card. Throw to show the message in that card. | { series, channels?, products?, devices? } | – |
currencyCodeThe shop currency, like "USD". | string | – |
nowThe current instant as ISO. Decides today, the incomplete point and future hours. | string | – |
timeZoneThe shop’s IANA time zone. Days and hours are counted in it. | string | "UTC" |
value{ preset, range: { start, end }, compare }. Controlled range. | SalesRangeValue | – |
defaultValueThe starting range when uncontrolled. | SalesRangeValue | last 30 days vs previous period |
onValueChangeCalled when the range or comparison changes. | (value: SalesRangeValue) => void | – |
titleThe page heading, like a greeting. | ReactNode | – |
descriptionA line under the heading. | ReactNode | – |
actionsExtra controls next to the range picker. | ReactNode | – |
metricsWhich KPI cards to show, in order. | SalesMetric[] | all six |
chartMetricsMetrics the chart can switch between. | SalesMetric[] | ["sales", "orders", "averageOrderValue"] |
metricDefinitionsRename a metric, change its tooltip, or set polarity to "down" when a fall is good. | Partial<Record<SalesMetric, { label, description, polarity }>> | – |
liveData for the live view card. Leave out to hide it. | LiveViewProps | "loading" | null | – |
onShareStoreShows Share your store on an empty chart. | () => void | – |
localeLocale for numbers and dates. | Intl.LocalesArgument | "en-US" |
labelsWords for captions, card titles, the picker and the chart. | Partial<SalesOverviewLabels> | – |
DateRangePicker
Also takes div props.
| Prop | Typ | Standard |
|---|---|---|
todayToday in the shop’s time zone. Later days can’t be picked. | string | – |
valueControlled value. | SalesRangeValue | – |
defaultValueUncontrolled starting value. | SalesRangeValue | – |
onValueChangeCalled with the new preset, range and comparison. | (value: SalesRangeValue) => void | – |
presetstoday, yesterday, last_7_days, last_30_days, last_90_days, month_to_date and custom. | DateRangePreset[] | all |
compareOptionsOptions in the compare-to select. | CompareMode[] | ["previous_period", "previous_year", "none"] |
timeZoneNamed in the picker so merchants know which day “today” is. | string | – |
KpiCard
Also takes div props.
| Prop | Typ | Standard |
|---|---|---|
labelThe metric name. | string | – |
descriptionThe definition shown in the info tooltip. | string | – |
valueThe value for the period. | number | null | – |
previousThe comparison value. Leave out when not comparing. | number | null | – |
formatHow the rolling value is formatted. | Intl.NumberFormatOptions | – |
polarityWhich direction is good. Sets the chip color. | "up" | "down" | "neutral" | "up" |
comparisonLabelThe caption, like “vs Aug 23–Sep 21”. | string | null | – |
points{ label, value, state?, previous?, previousLabel? } for the sparkline and hover caption. | KpiPoint[] | – |
statusLoading shows a skeleton the same size. | "ready" | "loading" | "unavailable" | "error" | "ready" |
messageWhy the metric is unavailable or failed. | string | – |
SalesChart
Also takes div props.
| Prop | Typ | Standard |
|---|---|---|
modelFrom buildSalesModel(query, series, currencyCode). | SalesModel | null | – |
statusLoading shows a skeleton, error shows the message with Try again. | "loading" | "ready" | "error" | "ready" |
onRetryCalled by Try again. | () => void | – |
metricControlled metric. Also defaultMetric and onMetricChange. | SalesMetric | – |
viewControlled view. Also defaultView and onViewChange. | "chart" | "table" | – |
onShareStoreAction in the empty state. | () => void | – |
LiveView
Also takes div props.
| Prop | Typ | Standard |
|---|---|---|
visitorsVisitors right now. Changes roll gently. | number | – |
ordersTodayOrders so far today. | number | – |
salesTodaySales so far today. | Money | – |
recentOrders{ id, name, customer, total, items, at, location? }. toLiveOrder(order) maps an Order. New ids slide in at the top. | LiveOrder[] | – |
statusShows Reconnecting… when your socket drops. | "live" | "reconnecting" | "live" |
nowUsed for “4 min ago”. | string | – |
| Prop | Typ | Standard |
|---|---|---|
dateThe bucket: 2026-10-21 for a day or week, 2026-10-21T14 for an hour. | string | – |
salesTotal sales in the bucket. | Money | – |
ordersOrders in the bucket. | number | – |
sessionsOptional. Without it, conversion rate and sessions show Not available. | number | – |
customers, returningCustomersOptional. Drive the returning customer rate. | number | – |
| Taste | Aktion |
|---|---|
| ←→ | With the chart focused, move the crosshair to the previous or next point. Every sparkline follows. |
| HomeEnd | Jump to the first or latest point. |
| Esc | Hide the crosshair. |
| ↑↓ | Move between presets in the date range picker. |
- Each KPI card is a group named with its full reading, like “Total sales, $113,661.39, Up 5.5%, an improvement, vs Aug 23–Sep 21”. Sparklines are hidden from screen readers, and the change is never shown by color alone: an arrow and a word carry it.
- The chart is focusable. Arrow keys move point by point and a polite live region reads the date, the value and the comparison. View as table shows the same data as a real table.
- A polite announcement reads the new range and its total once data loads, failed cards use role="alert", and new live orders are announced as they arrive. Visitor counts aren’t announced, so screen readers aren’t flooded.
- Numbers roll only when they change and jump straight to the new value with reduced motion. Nothing pulses, and the live dot is static.
Gebaut mit
Die kostenlosen HextaUI-Komponenten, aus denen Sales Overview besteht. Jede lässt sich einzeln installieren.
Code
11 Dateien, hinzugefügt zu components/blocks/sales-overview.