Skip to contentVibraUI
Dashboards

E-commerce vocabulary

The E-commerce dashboard's words and its rule for a sale: how an order's and a product's status are toned and called, which orders count as sold, and where an order, a product and a customer live.

One module the order, product and catalogue pages and the customer record print from, so paid wears the same tone wherever an order is listed and a sale means the same thing on the catalogue and on a product's page. Plain constants and pure functions: the entity types it reads are erased, so a client island and a server file can both import it without the store coming along. salesBetween takes the orders rather than reading db, and counts each line at the price its order charged, never today's.

Install

npx shadcn@latest add @vibra/vocabulary-ecommerce

Needs the @vibra registry in your components.json — set it up once.

Props

PropTypeDefaultDescription
ORDER_STATUS_MAPRecord<Order["status"], StatusVariant>—The tone each order status wears on a StatusBadge: paid is information, fulfilled success, a refund a warning.
PAYMENT_LABELRecord<Order["paymentMethod"], string>—How each way an order is paid reads — Card, Wallet, Bank, Cash, Voucher — written out, never the stored value under a CSS capital.
PRODUCT_STATUS_MAP / PRODUCT_STATUS_LABELSRecord<Product["status"], …>—A product's status as the catalogue prints it, and its tone.
SOLD_STATUSES / isSoldreadonly Order["status"][] / (order) => boolean—The rule for a sale: paid or fulfilled. A pending order, a cancelled cart and a refund are not sales.
salesBetween(orders: readonly Order[], from: number, to: number) => Map<string, ProductSales>—Units and cents sold per product by the sold orders placed after from and at or before to (epoch ms); a product that sold nothing is absent.
orderHref / productHref / customerHref(id: string) => string—An order's, a product's and a customer's own page, from the dashboard's route table.

Dependencies

Source

lib/dashboards/ecommerce/vocabulary.ts
import { ROUTES } from "@/lib/dashboards/ecommerce/nav"
import { type Order, type Product } from "@/lib/sample-data"
import { type StatusVariant } from "@/components/ui/status-badge"

/**
 * The E-commerce dashboard's words, and its one rule about a sale: how an
 * order's and a product's status are toned and called, which orders count as
 * sold, and where an order, a product and a customer live. One module the
 * pages share, so "paid" wears the same tone on the order book, an order and
 * a product's history, and a sale means the same thing on the catalogue and
 * on a product's page. Plain constants and pure functions — the entity types
 * above are erased — so a client island and a server file can both import it
 * without the store coming along.
 */

/**
 * How an order's status is toned wherever it is shown. Paid is money in hand
 * with the goods not yet gone, so it reads as information, not success; a
 * refund is money gone back, a warning rather than a failure.
 */
export const ORDER_STATUS_MAP: Record<Order["status"], StatusVariant> = {
  pending: "warning",
  paid: "info",
  fulfilled: "success",
  refunded: "warning",
  cancelled: "danger",
}

export const PRODUCT_STATUS_LABELS: Record<Product["status"], string> = { active: "Active", archived: "Archived" }

/**
 * Each way an order is paid, as a reader reads it: the words the order book
 * uses, so a row never prints the stored value under a CSS capital.
 */
export const PAYMENT_LABEL: Record<Order["paymentMethod"], string> = {
  card: "Card",
  wallet: "Wallet",
  bank: "Bank",
  cash: "Cash",
  voucher: "Voucher",
}

export const PRODUCT_STATUS_MAP: Record<Product["status"], StatusVariant> = { active: "success", archived: "neutral" }

/**
 * The order states whose goods count as sold: the money was taken and kept.
 * A pending order has not been paid for, and a cancelled cart and a refund are
 * not sales.
 */
export const SOLD_STATUSES: readonly Order["status"][] = ["paid", "fulfilled"]

export function isSold(order: Pick<Order, "status">): boolean {
  return SOLD_STATUSES.includes(order.status)
}

/** What one product sold over a window: its units, and what those lines charged in cents. */
export type ProductSales = { units: number; cents: number }

/**
 * What each product sold between two instants, in epoch milliseconds: the
 * lines of every sold order placed after `from` and at or before `to`. Each
 * line is counted at the price its order charged, never today's. Products
 * that sold nothing are absent from the map.
 */
export function salesBetween(orders: readonly Order[], from: number, to: number): Map<string, ProductSales> {
  const sales = new Map<string, ProductSales>()
  for (const order of orders) {
    if (!isSold(order)) continue
    const placed = order.placedAt.getTime()
    if (placed <= from || placed > to) continue
    for (const item of order.items) {
      const sale = sales.get(item.productId) ?? { units: 0, cents: 0 }
      sale.units += item.qty
      sale.cents += item.qty * item.unitCents
      sales.set(item.productId, sale)
    }
  }
  return sales
}

/** An order's own page, from the dashboard's route table. */
export const orderHref = (id: string) => ROUTES.order.replace("[id]", id)

/** A product's own page. */
export const productHref = (id: string) => ROUTES.product.replace("[id]", id)

/** A customer's record. */
export const customerHref = (id: string) => ROUTES.customer.replace("[id]", id)