Added Client Portal(Working)
Build & Auto-Repair / build (push) Has been cancelled

This commit is contained in:
JCBSComputer
2026-07-01 13:34:58 +02:00
parent 00bda54695
commit 9bfd4e47ee
9 changed files with 461 additions and 42 deletions
+82 -7
View File
@@ -2,32 +2,48 @@
import { useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import { FolderOpen, FileText, AlertCircle } from "lucide-react"
import { FolderOpen, FileText, AlertCircle, DollarSign, TrendingUp, AlertTriangle, Calendar } from "lucide-react"
interface Deadline {
type: string
id: string
name: string
date: string
status_name: string
}
export default function ClientDashboard() {
const router = useRouter()
const [counts, setCounts] = useState({ projects: 0, invoices: 0, tickets: 0 })
const [stats, setStats] = useState({ totalBilled: 0, totalPaid: 0, totalOverdue: 0, upcomingDeadlines: [] as Deadline[] })
const [name, setName] = useState("")
const safeJson = (r: Response) => r.ok ? r.json() : Promise.reject()
useEffect(() => {
fetch("/client-portal/api/projects", { credentials: "include" })
.then((r) => r.json())
.then(safeJson)
.then((d) => setCounts((prev) => ({ ...prev, projects: d.projects?.length || 0 })))
.catch(() => {})
fetch("/client-portal/api/invoices", { credentials: "include" })
.then((r) => r.json())
.then(safeJson)
.then((d) => setCounts((prev) => ({ ...prev, invoices: d.invoices?.length || 0 })))
.catch(() => {})
fetch("/client-portal/api/support", { credentials: "include" })
.then((r) => r.json())
.then(safeJson)
.then((d) => setCounts((prev) => ({ ...prev, tickets: d.tickets?.length || 0 })))
.catch(() => {})
fetch("/client-portal/api/dashboard/stats", { credentials: "include" })
.then(safeJson)
.then((d) => setStats(d))
.catch(() => {})
fetch("/api/auth/me", { credentials: "include" })
.then((r) => r.json())
.then((d) => setName(d.firstName || d.email || "Client"))
.then(safeJson)
.then((d) => setName(d.user?.firstName || d.user?.email || "Client"))
.catch(() => {})
}, [])
@@ -37,12 +53,18 @@ export default function ClientDashboard() {
{ label: "Support Tickets", value: counts.tickets, icon: AlertCircle, color: "#f59e0b", href: "/client-portal/support" },
]
const formatCurrency = (amount: number) => {
const num = Number(amount) || 0
return `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
return (
<div>
<h1 className="text-xl font-bold text-white mb-1">Welcome, {name}</h1>
<p className="text-sm text-[#6a6a75] mb-6">Your project overview at a glance</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{/* Count cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
{cards.map((card) => {
const Icon = card.icon
return (
@@ -62,6 +84,59 @@ export default function ClientDashboard() {
)
})}
</div>
{/* Financial stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5">
<div className="flex items-center gap-2 mb-2">
<DollarSign className="h-4 w-4 text-[#1BB0CE]" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Total Billed</span>
</div>
<p className="text-xl font-bold text-white">{formatCurrency(stats.totalBilled)}</p>
</div>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5">
<div className="flex items-center gap-2 mb-2">
<TrendingUp className="h-4 w-4 text-emerald-400" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Total Paid</span>
</div>
<p className="text-xl font-bold text-emerald-400">{formatCurrency(stats.totalPaid)}</p>
</div>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5">
<div className="flex items-center gap-2 mb-2">
<AlertTriangle className="h-4 w-4 text-red-400" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Overdue</span>
</div>
<p className="text-xl font-bold text-red-400">{formatCurrency(stats.totalOverdue)}</p>
</div>
</div>
{/* Upcoming Deadlines */}
{stats.upcomingDeadlines.length > 0 && (
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5">
<div className="flex items-center gap-2 mb-4">
<Calendar className="h-4 w-4 text-[#f59e0b]" />
<span className="text-sm font-semibold text-white">Upcoming Deadlines</span>
</div>
<div className="space-y-3">
{(stats.upcomingDeadlines || []).map((d, i) => (
<div key={`${d.type}-${d.id}-${i}`} className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-2 h-2 rounded-full ${
d.type === "invoice" ? "bg-[#a855f7]" : "bg-[#1BB0CE]"
}`} />
<div>
<p className="text-sm text-white">{d.name}</p>
<p className="text-[11px] text-[#6a6a75] capitalize">{d.type} &middot; {d.status_name?.replace("_", " ")}</p>
</div>
</div>
<span className="text-xs text-[#6a6a75]">
{new Date(d.date).toLocaleDateString()}
</span>
</div>
))}
</div>
</div>
)}
</div>
)
}