This commit implements the core admin dashboard functionality including NextAuth authentication, Cloudflare D1 database integration with complete schema, and Cloudflare R2 file upload system for portfolio images. Features include artist management, appointment scheduling, and data migration capabilities.
146 lines
5.1 KiB
TypeScript
146 lines
5.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getServerSession } from 'next-auth'
|
|
import { authOptions } from '@/lib/auth'
|
|
import { getDB } from '@/lib/db'
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export async function GET(request: NextRequest, { params }: { params?: any } = {}, context?: any) {
|
|
try {
|
|
const session = await getServerSession(authOptions)
|
|
if (!session?.user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const db = getDB(context?.env)
|
|
|
|
// Get artist statistics
|
|
const artistStats = await db.prepare(`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active,
|
|
SUM(CASE WHEN is_active = 0 THEN 1 ELSE 0 END) as inactive
|
|
FROM artists
|
|
`).first()
|
|
|
|
// Get appointment statistics
|
|
const appointmentStats = await db.prepare(`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN status = 'PENDING' THEN 1 ELSE 0 END) as pending,
|
|
SUM(CASE WHEN status = 'CONFIRMED' THEN 1 ELSE 0 END) as confirmed,
|
|
SUM(CASE WHEN status = 'IN_PROGRESS' THEN 1 ELSE 0 END) as inProgress,
|
|
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as completed,
|
|
SUM(CASE WHEN status = 'CANCELLED' THEN 1 ELSE 0 END) as cancelled,
|
|
SUM(CASE WHEN strftime('%Y-%m', start_time) = strftime('%Y-%m', 'now') THEN 1 ELSE 0 END) as thisMonth,
|
|
SUM(CASE WHEN strftime('%Y-%m', start_time) = strftime('%Y-%m', 'now', '-1 month') THEN 1 ELSE 0 END) as lastMonth,
|
|
SUM(CASE WHEN status = 'COMPLETED' THEN COALESCE(total_amount, 0) ELSE 0 END) as revenue
|
|
FROM appointments
|
|
`).first()
|
|
|
|
// Get portfolio statistics
|
|
const portfolioStats = await db.prepare(`
|
|
SELECT
|
|
COUNT(*) as totalImages,
|
|
SUM(CASE WHEN date(created_at) >= date('now', '-7 days') THEN 1 ELSE 0 END) as recentUploads
|
|
FROM portfolio_images
|
|
WHERE is_public = 1
|
|
`).first()
|
|
|
|
// Get file upload statistics
|
|
const fileStats = await db.prepare(`
|
|
SELECT
|
|
COUNT(*) as totalUploads,
|
|
SUM(size) as totalSize,
|
|
SUM(CASE WHEN date(created_at) >= date('now', '-7 days') THEN 1 ELSE 0 END) as recentUploads
|
|
FROM file_uploads
|
|
`).first()
|
|
|
|
// Get monthly appointment data for the last 6 months
|
|
const monthlyData = await db.prepare(`
|
|
SELECT
|
|
strftime('%Y-%m', start_time) as month,
|
|
COUNT(*) as appointments,
|
|
SUM(CASE WHEN status = 'COMPLETED' THEN COALESCE(total_amount, 0) ELSE 0 END) as revenue
|
|
FROM appointments
|
|
WHERE start_time >= date('now', '-6 months')
|
|
GROUP BY strftime('%Y-%m', start_time)
|
|
ORDER BY month
|
|
`).all()
|
|
|
|
// Format monthly data
|
|
const formattedMonthlyData = (monthlyData.results || []).map((row: any) => ({
|
|
month: new Date(row.month + '-01').toLocaleDateString('en-US', { month: 'short', year: 'numeric' }),
|
|
appointments: row.appointments || 0,
|
|
revenue: row.revenue || 0,
|
|
}))
|
|
|
|
// Create status distribution data
|
|
const statusData = [
|
|
{
|
|
name: 'Pending',
|
|
value: (appointmentStats as any)?.pending || 0,
|
|
color: '#f59e0b',
|
|
},
|
|
{
|
|
name: 'Confirmed',
|
|
value: (appointmentStats as any)?.confirmed || 0,
|
|
color: '#3b82f6',
|
|
},
|
|
{
|
|
name: 'In Progress',
|
|
value: (appointmentStats as any)?.inProgress || 0,
|
|
color: '#10b981',
|
|
},
|
|
{
|
|
name: 'Completed',
|
|
value: (appointmentStats as any)?.completed || 0,
|
|
color: '#6b7280',
|
|
},
|
|
{
|
|
name: 'Cancelled',
|
|
value: (appointmentStats as any)?.cancelled || 0,
|
|
color: '#ef4444',
|
|
},
|
|
].filter(item => item.value > 0) // Only include statuses with values
|
|
|
|
const stats = {
|
|
artists: {
|
|
total: (artistStats as any)?.total || 0,
|
|
active: (artistStats as any)?.active || 0,
|
|
inactive: (artistStats as any)?.inactive || 0,
|
|
},
|
|
appointments: {
|
|
total: (appointmentStats as any)?.total || 0,
|
|
pending: (appointmentStats as any)?.pending || 0,
|
|
confirmed: (appointmentStats as any)?.confirmed || 0,
|
|
inProgress: (appointmentStats as any)?.inProgress || 0,
|
|
completed: (appointmentStats as any)?.completed || 0,
|
|
cancelled: (appointmentStats as any)?.cancelled || 0,
|
|
thisMonth: (appointmentStats as any)?.thisMonth || 0,
|
|
lastMonth: (appointmentStats as any)?.lastMonth || 0,
|
|
revenue: (appointmentStats as any)?.revenue || 0,
|
|
},
|
|
portfolio: {
|
|
totalImages: (portfolioStats as any)?.totalImages || 0,
|
|
recentUploads: (portfolioStats as any)?.recentUploads || 0,
|
|
},
|
|
files: {
|
|
totalUploads: (fileStats as any)?.totalUploads || 0,
|
|
totalSize: (fileStats as any)?.totalSize || 0,
|
|
recentUploads: (fileStats as any)?.recentUploads || 0,
|
|
},
|
|
monthlyData: formattedMonthlyData,
|
|
statusData,
|
|
}
|
|
|
|
return NextResponse.json(stats)
|
|
} catch (error) {
|
|
console.error('Error fetching dashboard stats:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch dashboard statistics' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|