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>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user