Mikkel Manniche
Dansk English Deutsch

Vorlage · React · HTML

Sales floor

Ein Dashboard für ein Vertriebsteam in Tiles: eine segmentierte Zielanzeige, eine dunkle Karte zum laufenden Anruf, Balken pro Verkäufer und ein Umsatz-Donut.

Vorschau

React

Installiere die Vorlage mit shadcn. Sie landet als normale Komponenten in deinem Projekt, die du selbst anpasst.

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/sales-floor.json
npx shadcn@latest add @manniche/sales-floor
sales-floor.tsx
// A sales-floor screen: a segmented arc dial for the target, a live call card, performance bars and revenue by source.
import { Phone } from 'lucide-react'
import { cn } from '@/lib/utils'
import { BarChart, type BarPoint } from '@/registry/manniche/bar-chart/bar-chart'
import type { ValueFormat } from '@/registry/manniche/chart-kit/chart-utils'
import { DataTile } from '@/registry/manniche/data-tile/data-tile'
import { Dial, type DialZone } from '@/registry/manniche/dial/dial'
import { Donut, type DonutDatum } from '@/registry/manniche/donut/donut'

export type SalesFloorData = {
  target: { title: string; value: number; max: number; zones: DialZone[]; caption: string; format?: ValueFormat }
  call: { title: string; contact: string; company: string; status: string; duration: string; action: string }
  performance: { title: string; points: BarPoint[]; format?: ValueFormat; /** The bar to mark as current, if the bars are periods. Default none. */ current?: number | null }
  sources: { title: string; items: DonutDatum[]; format?: ValueFormat }
}

export type SalesFloorLabels = { note?: string }

export type SalesFloorProps = { data: SalesFloorData; labels?: SalesFloorLabels; onEndCall?: () => void; className?: string }

export function SalesFloor({ data, labels, onEndCall, className }: SalesFloorProps) {
  const note = labels?.note ?? 'Example data.'
  return (
    <div className={cn('@container w-full rounded-2xl bg-background p-3 text-foreground', className)}>
      <div className="grid grid-cols-1 gap-3 @2xl:grid-cols-6">
        <div className="@2xl:col-span-3">
          <DataTile className="h-full" title={data.call.title} inverted>
            <div className="flex flex-wrap items-center gap-3">
              <span aria-hidden className="grid size-11 place-items-center rounded-full bg-muted">
                <Phone className="size-5" />
              </span>
              <div className="min-w-0">
                <p className="truncate font-medium">{data.call.contact}</p>
                <p className="truncate text-sm opacity-70">{data.call.company}</p>
              </div>
              <span role="status" className="ml-auto text-sm whitespace-nowrap tabular-nums">
                {data.call.status} · {data.call.duration}
              </span>
            </div>
            <button
              type="button"
              onClick={onEndCall}
              className="mt-4 rounded-full bg-foreground px-4 py-2 text-sm font-medium text-background transition-transform active:scale-[0.97] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
            >
              {data.call.action}
            </button>
          </DataTile>
        </div>
        <div className="@2xl:col-span-3">
          <DataTile className="h-full" title={data.target.title} footer={note}>
            <Dial
              value={data.target.value}
              max={data.target.max}
              zones={data.target.zones}
              label={data.target.title}
              caption={data.target.caption}
              format={data.target.format}
              segments={24}
            />
          </DataTile>
        </div>
        <div className="@2xl:col-span-4">
          <DataTile className="h-full" title={data.performance.title} footer={note}>
            <BarChart data={data.performance.points} label={data.performance.title} format={data.performance.format} current={data.performance.current ?? null} />
          </DataTile>
        </div>
        <div className="@2xl:col-span-2">
          <DataTile className="h-full" title={data.sources.title} footer={note}>
            <Donut data={data.sources.items} label={data.sources.title} format={data.sources.format} />
          </DataTile>
        </div>
      </div>
    </div>
  )
}
sales-floor-demo.tsx
import { SalesFloor, type SalesFloorData } from '@/components/sales-floor'

// Example data only. All names and figures are invented.
const money = { currency: 'EUR', decimals: 0 }

const data: SalesFloorData = {
  call: { title: 'Live call', contact: 'Alex Example', company: 'Sample Co', status: 'Connected', duration: '04:12', action: 'End call' },
  target: {
    title: 'Monthly target',
    value: 62000,
    max: 100000,
    format: money,
    caption: 'of an example target',
    zones: [
      { label: 'Behind', to: 40000 },
      { label: 'On track', to: 80000 },
      { label: 'Ahead', to: 100000 },
    ],
  },
  performance: {
    title: 'Calls booked per rep',
    points: ['Ana', 'Ben', 'Cy', 'Di', 'Eli'].map((label, i) => ({ label, title: `${label} (example)`, value: [34, 28, 41, 22, 30][i] })),
  },
  sources: {
    title: 'Revenue by source',
    format: money,
    items: [
      { label: 'Outbound', value: 28000 },
      { label: 'Referral', value: 21000 },
      { label: 'Inbound', value: 13000 },
    ],
  },
}

export default function SalesFloorDemo() {
  return <SalesFloor data={data} />
}

HTML

Die ganze Seite als eine Datei mit eingebautem CSS und ohne JavaScript. Umschalter, Tabs und Filter funktionieren allein mit HTML und CSS.

In neuem Tab öffnen

Die Farbe in deiner eigenen App

Wenn shadcn bereits eingerichtet ist, ergeben diese Variablen dieselbe Farbe. Hell in :root, dunkel in .dark.

:root {
  --primary: oklch(0.52 0.2 262);
  --primary-foreground: oklch(0.99 0 0);
  --accent: oklch(0.95 0.034 262);
  --accent-foreground: oklch(0.36 0.140 262);
  --ring: oklch(0.52 0.2 262);
}

.dark {
  --primary: oklch(0.74 0.150 262);
  --primary-foreground: oklch(0.18 0.024 262);
  --accent: oklch(0.29 0.050 262);
  --accent-foreground: oklch(0.9 0.050 262);
  --ring: oklch(0.74 0.150 262);
}

Installieren

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/sales-floor.json

Abhängigkeiten

Dateien

  • sales-floor.tsx
  • sales-floor.html

Manniche Lab · Open Source

Kostenlose Werkzeuge für KI-Agenten und Webentwicklung.