Mikkel Manniche
Dansk English Deutsch

Template · React · HTML

People ops

A people dashboard in Tiles: a hiring funnel, today's schedule, a payroll list and an attendance dot matrix.

Preview

React

Install the template with shadcn. It lands in your project as ordinary components that you edit yourself.

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/people-ops.json
npx shadcn@latest add @manniche/people-ops
people-ops.tsx
// A people-ops screen: hiring funnel, today's schedule, payroll list and an attendance dot matrix.
import { cn } from '@/lib/utils'
import { BarChart, type BarPoint } from '@/registry/manniche/bar-chart/bar-chart'
import { DataTile } from '@/registry/manniche/data-tile/data-tile'
import { DotMatrix } from '@/registry/manniche/dot-matrix/dot-matrix'
import { TransactionList, type Transaction } from '@/registry/manniche/transaction-list/transaction-list'
import type { ValueFormat } from '@/registry/manniche/chart-kit/chart-utils'

export type PeopleOpsData = {
  funnel: { title: string; stages: BarPoint[] }
  today: { title: string; items: { id: string; time: string; title: string; who: string }[] }
  payroll: { title: string; items: Transaction[]; today?: string; format?: ValueFormat }
  attendance: { title: string; data: number[][]; rows: string[]; columns: string[] }
}

export type PeopleOpsLabels = { note?: string }

export type PeopleOpsProps = { data: PeopleOpsData; labels?: PeopleOpsLabels; className?: string }

export function PeopleOps({ data, labels, className }: PeopleOpsProps) {
  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.today.title} inverted>
            <ol className="flex flex-col gap-3">
              {data.today.items.map((it) => (
                <li key={it.id} className="flex items-baseline gap-3 text-sm">
                  <time className="w-12 shrink-0 tabular-nums opacity-70">{it.time}</time>
                  <span className="min-w-0 flex-1 truncate font-medium">{it.title}</span>
                  <span className="opacity-70">{it.who}</span>
                </li>
              ))}
            </ol>
          </DataTile>
        </div>
        <div className="@2xl:col-span-3">
          <DataTile className="h-full" title={data.funnel.title} footer={note}>
            <BarChart data={data.funnel.stages} label={data.funnel.title} current={null} />
          </DataTile>
        </div>
        <div className="@2xl:col-span-3">
          <DataTile className="h-full" title={data.payroll.title} footer={note}>
            <TransactionList data={data.payroll.items} label={data.payroll.title} today={data.payroll.today} format={data.payroll.format} />
          </DataTile>
        </div>
        <div className="@2xl:col-span-3">
          <DataTile className="h-full" title={data.attendance.title} footer={note}>
            <DotMatrix data={data.attendance.data} rows={data.attendance.rows} columns={data.attendance.columns} label={data.attendance.title} />
          </DataTile>
        </div>
      </div>
    </div>
  )
}
people-ops-demo.tsx
import { PeopleOps, type PeopleOpsData } from '@/components/people-ops'

// Example data only. All names and figures are invented.
const data: PeopleOpsData = {
  funnel: {
    title: 'Hiring funnel',
    stages: [
      { label: 'Applied', value: 120 },
      { label: 'Screen', value: 48 },
      { label: 'Interview', value: 20 },
      { label: 'Offer', value: 5 },
    ],
  },
  today: {
    title: "Today's schedule",
    items: [
      { id: 's1', time: '09:30', title: 'Sample stand-up', who: 'Team' },
      { id: 's2', time: '11:00', title: 'Demo interview', who: 'Alex E.' },
      { id: 's3', time: '14:00', title: 'Example onboarding', who: 'Sam S.' },
    ],
  },
  payroll: {
    title: 'Payroll',
    today: '2026-10-06',
    format: { currency: 'EUR', decimals: 0 },
    items: [
      { id: 'p1', name: 'Alex Example', amount: -3200, date: '2026-09-30', category: 'Salary' },
      { id: 'p2', name: 'Sam Sample', amount: -2900, date: '2026-09-30', category: 'Salary' },
      { id: 'p3', name: 'Robin Demo', amount: -2600, date: '2026-09-30', category: 'Salary' },
    ],
  },
  attendance: {
    title: 'Attendance',
    rows: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    columns: ['W1', 'W2', 'W3', 'W4'],
    data: [[8, 7, 8, 6], [7, 8, 8, 7], [8, 6, 7, 8], [7, 7, 8, 8], [5, 6, 4, 6]],
  },
}

export default function PeopleOpsDemo() {
  return <PeopleOps data={data} />
}

HTML

The whole page as one file with all CSS built in and no JavaScript. Toggles, tabs and filters work with HTML and CSS alone.

Open in a new tab

The colour in your own app

If you already have shadcn set up, these variables give the same colour. Light in :root, dark 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);
}

Install

npx shadcn@latest add https://mikkelmanniche.dk/lab/r/people-ops.json

Dependencies

    Files

    • people-ops.tsx
    • people-ops.html

    Manniche Lab · open source

    Free tools for AI agents and web development.