Files
property-management-network/components/dashboard/property-revenue-chart.tsx
T
Leon SerfatyandClaude Opus 4.8 857b9a7811 Initial import: property management SaaS + security hardening + admin dashboard
Property Management Network — Next.js 16 (App Router), Better Auth,
Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend.

Includes:
- Security hardening: access-control/IDOR fixes, TLS-by-default DB layer,
  constant-time cron auth, strict security headers, atomic AI quota gating,
  HTML/email output encoding, demo-backdoor disabled in production.
- Superadmin dashboard at /admin (overview/MRR, server-paginated users with
  ban/impersonate/plan/delete, billing, platform activity + admin audit log,
  AI usage, system health) via the Better Auth admin plugin.
- Seed/migration utility scripts under scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:36:07 -04:00

75 lines
3.4 KiB
TypeScript

"use client"
import { formatCurrency } from "@/lib/utils"
import { TrendingUp } from "lucide-react"
interface MonthData { label: string; revenue: number; expense: number }
export function PropertyRevenueChart({ data }: { data: MonthData[] }) {
const maxVal = Math.max(...data.map(m => Math.max(m.revenue, m.expense)), 1)
const totalRevenue = data.reduce((s, m) => s + m.revenue, 0)
const totalExpense = data.reduce((s, m) => s + m.expense, 0)
const net = totalRevenue - totalExpense
return (
<div className="rounded-xl border border-white/[0.06] bg-[#16161f] overflow-hidden">
<div className="flex items-center justify-between px-5 py-4 border-b border-white/[0.06]">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-indigo-400" />
<p className="text-sm font-semibold text-white">Revenue vs Expenses</p>
</div>
<p className={`text-sm font-bold tabular-nums ${net >= 0 ? "text-emerald-400" : "text-red-400"}`}>
{net >= 0 ? "+" : ""}{formatCurrency(net)} net
</p>
</div>
<div className="px-5 pt-4 pb-2">
<div className="flex items-end gap-3 h-28">
{data.map((m, i) => (
<div key={i} className="flex-1 flex flex-col items-center gap-0.5">
<div className="w-full flex items-end gap-0.5" style={{ height: "96px" }}>
<div
className="flex-1 rounded-t-sm bg-indigo-500/60 hover:bg-indigo-500/90 transition"
style={{ height: `${Math.max((m.revenue / maxVal) * 100, m.revenue > 0 ? 4 : 0)}%` }}
title={`Revenue: ${formatCurrency(m.revenue)}`}
/>
<div
className="flex-1 rounded-t-sm bg-rose-500/50 hover:bg-rose-500/80 transition"
style={{ height: `${Math.max((m.expense / maxVal) * 100, m.expense > 0 ? 4 : 0)}%` }}
title={`Expenses: ${formatCurrency(m.expense)}`}
/>
</div>
<span className="text-[9px] text-white/25">{m.label}</span>
</div>
))}
</div>
</div>
<div className="grid grid-cols-3 divide-x divide-white/[0.04] border-t border-white/[0.06]">
<div className="px-4 py-2.5 text-center">
<p className="text-xs font-bold text-emerald-400 tabular-nums">{formatCurrency(totalRevenue)}</p>
<p className="text-[10px] text-white/25">Revenue</p>
</div>
<div className="px-4 py-2.5 text-center">
<p className="text-xs font-bold text-rose-400 tabular-nums">{formatCurrency(totalExpense)}</p>
<p className="text-[10px] text-white/25">Expenses</p>
</div>
<div className="px-4 py-2.5 text-center">
<p className={`text-xs font-bold tabular-nums ${net >= 0 ? "text-emerald-400" : "text-red-400"}`}>{formatCurrency(net)}</p>
<p className="text-[10px] text-white/25">Net Income</p>
</div>
</div>
{/* Legend */}
<div className="flex items-center justify-center gap-4 pb-3 pt-1">
<div className="flex items-center gap-1.5 text-[10px] text-white/30">
<span className="h-2 w-3 rounded-sm bg-indigo-500/60 inline-block" /> Revenue
</div>
<div className="flex items-center gap-1.5 text-[10px] text-white/30">
<span className="h-2 w-3 rounded-sm bg-rose-500/50 inline-block" /> Expenses
</div>
</div>
</div>
)
}