Files
monitoring-app/src/frontend/routes/dashboard.tsx
amal c66ce4a39b upd: auth
Deskripsi:
-update login
- update struktur database

No Issues
2026-04-15 11:17:04 +08:00

195 lines
7.0 KiB
TypeScript

import { AppCard } from '@/frontend/components/AppCard'
import { DashboardLayout } from '@/frontend/components/DashboardLayout'
import { StatsCard } from '@/frontend/components/StatsCard'
import { useSession } from '@/frontend/hooks/useAuth'
import {
Badge,
Button,
Container,
Group,
Loader,
Paper,
SimpleGrid,
Stack,
Table,
Text,
Title,
} from '@mantine/core'
import { useQuery } from '@tanstack/react-query'
import { createFileRoute, Link, redirect } from '@tanstack/react-router'
import { TbApps, TbChevronRight, TbMessageReport, TbUsers } from 'react-icons/tb'
export const Route = createFileRoute('/dashboard')({
beforeLoad: async ({ context }) => {
try {
const data = await context.queryClient.ensureQueryData({
queryKey: ['auth', 'session'],
queryFn: () => fetch('/api/auth/session', { credentials: 'include' }).then((r) => r.json()),
})
if (!data?.user) throw redirect({ to: '/login' })
} catch (e) {
if (e instanceof Error) throw redirect({ to: '/login' })
throw e
}
},
component: DashboardPage,
})
function DashboardPage() {
const { data: sessionData } = useSession()
const user = sessionData?.user
const { data: stats, isLoading: statsLoading } = useQuery({
queryKey: ['dashboard', 'stats'],
queryFn: () => fetch('/api/dashboard/stats').then((r) => r.json()),
})
const { data: apps, isLoading: appsLoading } = useQuery({
queryKey: ['apps'],
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 (
<DashboardLayout>
<Container size="xl" py="lg">
<Stack gap="xl">
<Group justify="space-between" align="center">
<Stack gap={0}>
<Title order={2} className="gradient-text">Overview Dashboard</Title>
<Text size="sm" c="dimmed">Welcome back, {user?.name}. Here is what's happening today.</Text>
</Stack>
{/* <Button
variant="gradient"
gradient={{ from: '#2563EB', to: '#7C3AED', deg: 135 }}
leftSection={<TbApps size={18} />}
radius="md"
component={Link}
to="/apps"
>
Manage All Apps
</Button> */}
</Group>
{statsLoading ? (
<Loader size="xl" type="dots" mx="auto" mt="xl" />
) : (
<SimpleGrid cols={{ base: 1, sm: 3 }}>
<StatsCard
title="Total Applications"
value={stats?.totalApps || 0}
icon={TbApps}
color="brand-blue"
// trend={{ value: stats?.trends?.totalApps.toString() || '0', positive: true }}
/>
<StatsCard
title="New Errors"
value={stats?.newErrors || 0}
icon={TbMessageReport}
color="brand-purple"
// trend={{ value: stats?.trends?.newErrors.toString() || '0', positive: false }}
/>
<StatsCard
title="Users"
value={stats?.activeUsers || 0}
icon={TbUsers}
color="teal"
// trend={{ value: stats?.trends?.activeUsers.toString() || '0', positive: true }}
/>
</SimpleGrid>
)}
<Group justify="space-between" mt="md">
<Title order={3}>Registered Applications</Title>
<Button variant="subtle" color="brand-blue" rightSection={<TbChevronRight size={16} />} component={Link} to="/apps">
View All Apps
</Button>
</Group>
{appsLoading ? (
<Loader size="xl" type="dots" mx="auto" />
) : (
<SimpleGrid cols={{ base: 1, md: 3 }}>
{apps?.map((app: any) => (
<AppCard key={app.id} {...app} />
))}
</SimpleGrid>
)}
<Group justify="space-between" mt="md">
<Title order={3}>Recent Error Reports</Title>
<Button variant="subtle" color="brand-blue" rightSection={<TbChevronRight size={16} />} component={Link} to="/bug-reports">
View All Errors
</Button>
</Group>
<Paper withBorder radius="2xl" className="glass" p="md">
<Table className="data-table" verticalSpacing="md">
<Table.Thead>
<Table.Tr>
<Table.Th>Application</Table.Th>
<Table.Th>Error Message</Table.Th>
<Table.Th>Version</Table.Th>
<Table.Th>Time</Table.Th>
<Table.Th>Severity</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{recentErrorsLoading ? (
<Table.Tr>
<Table.Td colSpan={5} align="center" py="xl">
<Loader size="sm" type="dots" />
</Table.Td>
</Table.Tr>
) : recentErrors.length === 0 ? (
<Table.Tr>
<Table.Td colSpan={5} align="center" py="xl">
<Text c="dimmed" size="sm">No recent errors found.</Text>
</Table.Td>
</Table.Tr>
) : recentErrors.map((error: any) => (
<Table.Tr key={error.id}>
<Table.Td>
<Text fw={600} size="sm" style={{ textTransform: 'uppercase' }}>{error.app}</Text>
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed" lineClamp={1}>{error.message}</Text>
</Table.Td>
<Table.Td>
<Badge variant="light" color="gray">v{error.version}</Badge>
</Table.Td>
<Table.Td>
<Text size="xs" c="dimmed">{formatTimeAgo(error.time)}</Text>
</Table.Td>
<Table.Td>
<Badge
color={error.severity === 'OPEN' ? 'red' : error.severity === 'IN_PROGRESS' || error.severity === 'ON_HOLD' ? 'orange' : 'yellow'}
variant="dot"
>
{error.severity.toUpperCase()}
</Badge>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Paper>
</Stack>
</Container>
</DashboardLayout>
)
}