Skabelon · React · HTML
Rental portfolio
Et ejendomsdashboard i Tiles: porteføljens tal, en arealgraf over indtægter mod udbetalinger, en skive for indbetalt husleje og seneste overførsler.
Forhåndsvisning
React
Installér skabelonen med shadcn. Den lægges i dit projekt som almindelige komponenter, som du selv retter i.
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/rental-portfolio.jsonnpx shadcn@latest add @manniche/rental-portfoliorental-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
Hele siden som én fil med al CSS indbygget og intet JavaScript. Knapper, faner og filtre virker med HTML og CSS alene.
Farven i din egen app
Har du allerede shadcn sat op, giver disse variabler samme farve. Lys i :root, mørk i .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ér
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/rental-portfolio.json