- Extensive updates to system configuration and deployment - Enhanced documentation and architecture improvements - Updated dependencies and build configurations - Improved service integrations and workflows 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { LeadStats, Lead } from '@/lib/db'
|
|
import StatsCards from '@/components/StatsCards'
|
|
import LeadsTable from '@/components/LeadsTable'
|
|
import ThemeToggle from '@/components/ThemeToggle'
|
|
import LeadsChart from '@/components/LeadsChart'
|
|
|
|
export default function DashboardPage() {
|
|
const [stats, setStats] = useState<LeadStats | null>(null)
|
|
const [leadsData, setLeadsData] = useState<{ leads: Lead[], pagination: any } | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const [statsResponse, leadsResponse] = await Promise.all([
|
|
fetch('/api/stats'),
|
|
fetch('/api/leads')
|
|
])
|
|
|
|
if (!statsResponse.ok || !leadsResponse.ok) {
|
|
throw new Error('Failed to fetch data')
|
|
}
|
|
|
|
const statsData = await statsResponse.json()
|
|
const leadsData = await leadsResponse.json()
|
|
|
|
setStats(statsData)
|
|
setLeadsData(leadsData)
|
|
} catch (error) {
|
|
console.error('Failed to fetch dashboard data:', error)
|
|
setError('Failed to load dashboard data. Please try again.')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
fetchData()
|
|
}, [])
|
|
|
|
if (loading) {
|
|
return (
|
|
<main className="min-h-screen p-chorus-lg flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-mulberry-600 mx-auto mb-4"></div>
|
|
<p className="text-lg text-carbon-600 dark:text-carbon-300">Loading dashboard...</p>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<main className="min-h-screen p-chorus-lg flex items-center justify-center">
|
|
<div className="text-center">
|
|
<p className="text-lg text-coral-600 mb-4">{error}</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="btn-primary px-chorus-lg py-chorus-md"
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (!stats || !leadsData) {
|
|
return (
|
|
<main className="min-h-screen p-chorus-lg flex items-center justify-center">
|
|
<p className="text-lg text-carbon-600 dark:text-carbon-300">No data available</p>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<main className="min-h-screen p-chorus-lg bg-gradient-to-b from-white via-sand-50 to-sand-100 dark:from-carbon-950 dark:via-carbon-900 dark:to-carbon-950">
|
|
<div className="max-w-7xl mx-auto">
|
|
{/* Theme Toggle */}
|
|
<ThemeToggle />
|
|
|
|
{/* Header */}
|
|
<div className="mb-chorus-xxl">
|
|
<h1 className="text-h2 font-logo font-thin text-carbon-950 dark:text-white mb-chorus-md">
|
|
CHORUS Dashboard
|
|
</h1>
|
|
<p className="text-lg text-carbon-600 dark:text-carbon-300">
|
|
Lead management and analytics for CHORUS Services
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<StatsCards stats={stats} />
|
|
|
|
{/* Lead Trends Chart */}
|
|
<LeadsChart stats={stats} />
|
|
|
|
{/* Source Breakdown */}
|
|
{stats.by_source.length > 0 && (
|
|
<div className="dashboard-card p-chorus-lg mb-chorus-xxl">
|
|
<h3 className="text-xl font-semibold text-carbon-900 dark:text-white mb-chorus-lg">
|
|
Lead Sources
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-chorus-md">
|
|
{stats.by_source.map((source) => (
|
|
<div
|
|
key={source.lead_source}
|
|
className="bg-sand-100 dark:bg-carbon-800 rounded-lg p-chorus-md border border-sand-200 dark:border-carbon-700 hover:shadow-md transition-shadow duration-200"
|
|
>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm font-medium text-carbon-700 dark:text-carbon-200">
|
|
{source.lead_source.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
</span>
|
|
<span className="text-lg font-bold text-carbon-900 dark:text-white">
|
|
{source.count}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Leads Table */}
|
|
<LeadsTable
|
|
initialLeads={leadsData.leads}
|
|
initialPagination={leadsData.pagination}
|
|
/>
|
|
</div>
|
|
</main>
|
|
)
|
|
} |