upd: overview desa

This commit is contained in:
2026-04-15 14:14:18 +08:00
parent c66ce4a39b
commit c67fc9a230
2 changed files with 50 additions and 60 deletions

View File

@@ -15,62 +15,35 @@ import {
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
import { TbMessageReport, TbHistory, TbExternalLink, TbBug } from 'react-icons/tb'
const mockErrors = [
{
id: 1,
message: 'NullPointerException at village_sync.dart:45',
village: 'Sukatani',
version: 'v1.2.0',
timestamp: '2026-04-01 14:30:15',
severity: 'critical',
stackTrace: 'at com.desa.sync.VillageManager.sync(VillageManager.java:45)\nat com.desa.sync.SyncService.onHandleIntent(SyncService.java:120)'
},
{
id: 2,
message: 'Failed to load citizen record session',
village: 'Sukamaju',
version: 'v1.1.8',
timestamp: '2026-04-01 14:15:22',
severity: 'high',
stackTrace: 'Error: Connection timeout reaching upstream citizen-db\n at HttpClient.get (network.dart:88)'
},
{
id: 3,
message: 'SocketException: Connection timed out',
village: 'Cikini',
version: 'v1.2.0',
timestamp: '2026-04-01 13:55:10',
severity: 'medium',
stackTrace: 'SocketException: OS Error: Connection timed out, errno = 110, address = 10.0.2.2, port = 54332'
},
{
id: 4,
message: 'UI Thread blocking > 500ms',
village: 'Beji',
version: 'v1.1.2',
timestamp: '2026-04-01 13:40:00',
severity: 'low',
stackTrace: 'ANR (Application Not Responding) detected in main thread.'
},
]
export interface ErrorDataTableProps {
appId?: string
}
export function ErrorDataTable() {
export function ErrorDataTable({ appId }: ErrorDataTableProps) {
const [opened, { open, close }] = useDisclosure(false)
const [selectedError, setSelectedError] = useState<any>(null)
const { data: bugsData, isLoading } = useQuery({
queryKey: ['bugs', appId],
queryFn: () => fetch(`/api/bugs?app=${appId || 'all'}&limit=10`).then((r) => r.json()),
})
const bugs = bugsData?.data || []
const handleRowClick = (error: any) => {
setSelectedError(error)
open()
}
const getSeverityColor = (sev: string) => {
switch(sev) {
case 'critical': return 'red'
case 'high': return 'orange'
case 'medium': return 'yellow'
switch(sev?.toUpperCase()) {
case 'OPEN': return 'red'
case 'IN_PROGRESS': return 'orange'
case 'ON_HOLD': return 'yellow'
default: return 'gray'
}
}
@@ -86,7 +59,7 @@ export function ErrorDataTable() {
</ThemeIcon>
<Text fw={700}>LATEST ERROR REPORTS</Text>
</Group>
<Button component={Link} to='/apps/desa-plus/errors' variant="subtle" size="compact-xs" color="blue" rightSection={<TbExternalLink size={14} />}>
<Button component={Link} to={appId ? `/apps/${appId}/errors` : '/bug-reports'} variant="subtle" size="compact-xs" color="blue" rightSection={<TbExternalLink size={14} />}>
View All Reports
</Button>
</Group>
@@ -97,37 +70,49 @@ export function ErrorDataTable() {
<Table.Thead bg="rgba(0,0,0,0.1)">
<Table.Tr>
<Table.Th px="xl">Error Message</Table.Th>
<Table.Th>Village</Table.Th>
<Table.Th>Reporter</Table.Th>
<Table.Th>App Version</Table.Th>
<Table.Th>Timestamp</Table.Th>
<Table.Th pr="xl">Severity</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{mockErrors.map((error) => (
{isLoading ? (
<Table.Tr>
<Table.Td colSpan={5} align="center" py="xl">
Loading errors...
</Table.Td>
</Table.Tr>
) : bugs.length === 0 ? (
<Table.Tr>
<Table.Td colSpan={5} align="center" py="xl">
No errors found.
</Table.Td>
</Table.Tr>
) : bugs.map((error: any) => (
<Table.Tr
key={error.id}
onClick={() => handleRowClick(error)}
style={{ cursor: 'pointer' }}
>
<Table.Td px="xl">
<Text size="sm" fw={600} lineClamp={1}>{error.message}</Text>
<Text size="sm" fw={600} lineClamp={1}>{error.description}</Text>
</Table.Td>
<Table.Td>
<Badge variant="dot" color="brand-blue" radius="sm">{error.village}</Badge>
<Badge variant="dot" color="brand-blue" radius="sm">{error.user?.name || error.userId || 'System'}</Badge>
</Table.Td>
<Table.Td>
<Text size="xs" fw={700} c="dimmed">{error.version}</Text>
<Text size="xs" fw={700} c="dimmed">{error.affectedVersion || 'N/A'}</Text>
</Table.Td>
<Table.Td>
<Group gap={6}>
<TbHistory size={12} color="gray" />
<Text size="xs" c="dimmed">{error.timestamp}</Text>
<Text size="xs" c="dimmed">{new Date(error.createdAt).toLocaleString()}</Text>
</Group>
</Table.Td>
<Table.Td pr="xl">
<Badge color={getSeverityColor(error.severity)} variant="light" size="sm">
{error.severity.toUpperCase()}
<Badge color={getSeverityColor(error.status)} variant="light" size="sm">
{(error.status || '').toUpperCase()}
</Badge>
</Table.Td>
</Table.Tr>
@@ -156,17 +141,17 @@ export function ErrorDataTable() {
<Stack p="lg" gap="xl">
<Box>
<Text size="xs" fw={700} c="dimmed" mb={4}>MESSAGE</Text>
<Text fw={700} size="lg" color="red">{selectedError.message}</Text>
<Text fw={700} size="lg" color="red">{selectedError.description}</Text>
</Box>
<SimpleGrid cols={2} spacing="lg">
<Box>
<Text size="xs" fw={700} c="dimmed" mb={4}>VILLAGE</Text>
<Text fw={600}>{selectedError.village}</Text>
<Text size="xs" fw={700} c="dimmed" mb={4}>REPORTER</Text>
<Text fw={600}>{selectedError.user?.name || selectedError.userId || 'System'}</Text>
</Box>
<Box>
<Text size="xs" fw={700} c="dimmed" mb={4}>APP VERSION</Text>
<Badge variant="outline">{selectedError.version}</Badge>
<Badge variant="outline">{selectedError.affectedVersion || 'N/A'}</Badge>
</Box>
</SimpleGrid>