diff --git a/src/app.ts b/src/app.ts index 643281c..3802ce9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -154,18 +154,38 @@ export function createApp() { }) // ─── Monitoring API ──────────────────────────────── - .get('/api/dashboard/stats', () => ({ - totalApps: 3, - newErrors: 185, - activeUsers: '24.5k', - trends: { totalApps: 1, newErrors: 12, activeUsers: 5.2 } - })) + .get('/api/dashboard/stats', async () => { + const newErrors = await prisma.bug.count({ where: { status: 'OPEN' } }) + const users = await prisma.user.count() + return { + totalApps: 1, + newErrors: newErrors, + activeUsers: users, + trends: { totalApps: 0, newErrors: 12, activeUsers: 5.2 } + } + }) - .get('/api/apps', () => [ - { id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: 12, version: '2.4.1' }, - // { id: 'e-commerce', name: 'E-Commerce', status: 'warning', users: 8900, errors: 45, version: '1.8.0' }, - // { id: 'fitness-app', name: 'Fitness App', status: 'error', users: 3200, errors: 128, version: '0.9.5' }, - ]) + .get('/api/dashboard/recent-errors', async () => { + const bugs = await prisma.bug.findMany({ + take: 5, + orderBy: { createdAt: 'desc' } + }) + return bugs.map(b => ({ + id: b.id, + app: b.app, + message: b.description, + version: b.affectedVersion, + time: b.createdAt.toISOString(), + severity: b.status + })) + }) + + .get('/api/apps', async () => { + const desaPlusErrors = await prisma.bug.count({ where: { app: { in: ['desa-plus', 'desa_plus'] }, status: 'OPEN' } }) + return [ + { id: 'desa-plus', name: 'Desa+', status: 'active', users: 12450, errors: desaPlusErrors, version: '2.4.1' }, + ] + }) .get('/api/apps/:appId', ({ params: { appId } }) => { const apps = { diff --git a/src/frontend/routes/dashboard.tsx b/src/frontend/routes/dashboard.tsx index a31dcb7..d82ab7c 100644 --- a/src/frontend/routes/dashboard.tsx +++ b/src/frontend/routes/dashboard.tsx @@ -36,12 +36,6 @@ export const Route = createFileRoute('/dashboard')({ component: DashboardPage, }) -const recentErrors = [ - { id: 1, app: 'Desa+', message: 'NullPointerException at village_sync.dart:45', version: '2.4.1', time: '2 mins ago', severity: 'critical' }, - { id: 2, app: 'E-Commerce', message: 'Failed to load checkout session', version: '1.8.0', time: '15 mins ago', severity: 'high' }, - { id: 3, app: 'Fitness App', message: 'SocketException: Connection timed out', version: '0.9.5', time: '1 hour ago', severity: 'medium' }, -] - function DashboardPage() { const { data: sessionData } = useSession() const user = sessionData?.user @@ -56,6 +50,20 @@ function DashboardPage() { queryFn: () => fetch('/api/apps').then((r) => r.json()), }) + const { data: recentErrors = [], isLoading: recentErrorsLoading } = useQuery({ + queryKey: ['dashboard', 'recent-errors'], + queryFn: () => fetch('/api/dashboard/recent-errors').then((r) => r.json()), + }) + + const formatTimeAgo = (dateStr: string) => { + const diff = new Date().getTime() - new Date(dateStr).getTime() + const minutes = Math.floor(diff / 60000) + if (minutes < 60) return `${minutes || 1} mins ago` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours} hours ago` + return `${Math.floor(hours / 24)} days ago` + } + return ( @@ -86,21 +94,21 @@ function DashboardPage() { value={stats?.totalApps || 0} icon={TbApps} color="brand-blue" - trend={{ value: stats?.trends?.totalApps.toString() || '0', positive: true }} + // trend={{ value: stats?.trends?.totalApps.toString() || '0', positive: true }} /> )} @@ -124,7 +132,7 @@ function DashboardPage() { Recent Error Reports - @@ -141,23 +149,35 @@ function DashboardPage() { - {recentErrors.map((error) => ( + {recentErrorsLoading ? ( + + + + + + ) : recentErrors.length === 0 ? ( + + + No recent errors found. + + + ) : recentErrors.map((error: any) => ( - {error.app} + {error.app} - {error.message} + {error.message} v{error.version} - {error.time} + {formatTimeAgo(error.time)} {error.severity.toUpperCase()}