Mikkel Manniche
Dansk English Deutsch

Vorlage · React · HTML

Rental portfolio

Ein Immobilien-Dashboard in Tiles: Portfoliozahlen, ein Flächendiagramm von Einnahmen gegen Auszahlungen, eine Anzeige für eingegangene Mieten und letzte Überweisungen.

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/rental-portfolio.json
npx shadcn@latest add @manniche/rental-portfolio
rental-portfolio.tsx
// A rental portfolio screen: portfolio figures, income vs payout, rent collection and recent transfers.
import { cn } from '@/lib/utils'
import { AreaChart, type AreaPoint } from '@/registry/manniche/area-chart/area-chart'
import { BigNumber } from '@/registry/manniche/chart-kit/chart-kit'
import type { ValueFormat } from '@/registry/manniche/chart-kit/chart-utils'
import { DataTile, TileFact } from '@/registry/manniche/data-tile/data-tile'
import { Dial, type DialZone } from '@/registry/manniche/dial/dial'
import { TransactionList, type Transaction } from '@/registry/manniche/transaction-list/transaction-list'

export type RentalPortfolioData = {
  figures: { id: string; title: string; value: number; format?: ValueFormat }[]
  income: { title: string; points: AreaPoint[]; format?: ValueFormat }
  collection: { title: string; value: number; zones: DialZone[]; caption: string }
  transfers: { title: string; items: Transaction[]; today?: string; format?: ValueFormat }
}

export type RentalPortfolioLabels = { note?: string }

export type RentalPortfolioProps = { data: RentalPortfolioData; labels?: RentalPortfolioLabels; className?: string }

export function RentalPortfolio({ data, labels, className }: RentalPortfolioProps) {
  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 @lg:grid-cols-3 @3xl:grid-cols-6">
        {data.figures.map((f, i) => (
          <div key={f.id} className="@3xl:col-span-2">
            <DataTile className="h-full" title={f.title} inverted={i === 0} density="compact">
              <TileFact>
                <BigNumber value={f.value} format={f.format} size="lg" />
              </TileFact>
            </DataTile>
          </div>
        ))}
        <div className="@lg:col-span-3 @3xl:col-span-4">
          <DataTile className="h-full" title={data.income.title} footer={note}>
            <AreaChart data={data.income.points} label={data.income.title} format={data.income.format} compare />
          </DataTile>
        </div>
        <div className="@lg:col-span-3 @3xl:col-span-2">
          <DataTile className="h-full" title={data.collection.title} footer={note}>
            <Dial value={data.collection.value} min={0} max={100} zones={data.collection.zones} label={data.collection.title} caption={data.collection.caption} format={{ suffix: '%' }} />
          </DataTile>
        </div>
        <div className="@lg:col-span-3 @3xl:col-span-6">
          <DataTile className="h-full" title={data.transfers.title} footer={note}>
            <TransactionList data={data.transfers.items} label={data.transfers.title} today={data.transfers.today} format={data.transfers.format} />
          </DataTile>
        </div>
      </div>
    </div>
  )
}
rental-portfolio-demo.tsx
import { RentalPortfolio, type RentalPortfolioData } from '@/components/rental-portfolio'

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

const data: RentalPortfolioData = {
  figures: [
    { id: 'v', title: 'Portfolio value', value: 1240000, format: money },
    { id: 'u', title: 'Units', value: 14 },
    { id: 'r', title: 'Monthly rent', value: 9800, format: money },
  ],
  income: {
    title: 'Income vs payout',
    format: money,
    points: ['May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct'].map((label, i) => ({ label, value: [8800, 9100, 9300, 9500, 9700, 9800][i], previous: [7600, 7900, 8100, 8300, 8500, 8700][i] })),
  },
  collection: {
    title: 'Rent collected',
    value: 92,
    caption: 'of rent due this month',
    zones: [
      { label: 'Late', to: 70 },
      { label: 'Mostly in', to: 90 },
      { label: 'Collected', to: 100 },
    ],
  },
  transfers: {
    title: 'Recent transfers',
    today: '2026-10-06',
    format: money,
    items: [
      { id: 't1', name: 'Example tenant, unit 3', amount: 850, date: '2026-10-05', category: 'Rent' },
      { id: 't2', name: 'Sample Repairs', amount: -240, date: '2026-10-04', category: 'Repair' },
      { id: 't3', name: 'Owner payout', amount: -3000, date: '2026-10-01', category: 'Payout' },
    ],
  },
}

export default function RentalPortfolioDemo() {
  return <RentalPortfolio 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/rental-portfolio.json

Abhängigkeiten

    Dateien

    • rental-portfolio.tsx
    • rental-portfolio.html

    Manniche Lab · Open Source

    Kostenlose Werkzeuge für KI-Agenten und Webentwicklung.