feat: improve UI/UX consistency across all dashboard pages
Apply uniform design system across all routes and components: - Consistent header pattern with gradient-text titles, dimmed subtitles - Loader type="dots" replacing text-based loading states - Icon + text empty/error states with Paper+glass containers - Full STATUS_COLOR/STATUS_LABEL maps for all BugStatus values - dayjs timestamps, Tooltip on action icons, size="sm" on badges/pagination - Modals with overlayProps blur and gradient save buttons - Replace left-border Papers with clean Stack headers - Translate all remaining Indonesian UI strings to English - New monitoring-themed SVG logo and redesigned splash screen
This commit is contained in:
@@ -6,16 +6,21 @@ import {
|
||||
Divider,
|
||||
Drawer,
|
||||
Group,
|
||||
Loader,
|
||||
Paper,
|
||||
ScrollArea,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
Title
|
||||
ThemeIcon,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core'
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import dayjs from 'dayjs'
|
||||
import { useState } from 'react'
|
||||
import { TbBug, TbExternalLink, TbHistory, TbMessageReport } from 'react-icons/tb'
|
||||
|
||||
@@ -23,6 +28,23 @@ export interface ErrorDataTableProps {
|
||||
appId?: string
|
||||
}
|
||||
|
||||
const STATUS_COLOR: Record<string, string> = {
|
||||
OPEN: 'red',
|
||||
IN_PROGRESS: 'blue',
|
||||
ON_HOLD: 'orange',
|
||||
RESOLVED: 'teal',
|
||||
RELEASED: 'green',
|
||||
CLOSED: 'gray',
|
||||
}
|
||||
const STATUS_LABEL: Record<string, string> = {
|
||||
OPEN: 'Open',
|
||||
ON_HOLD: 'On Hold',
|
||||
IN_PROGRESS: 'In Progress',
|
||||
RESOLVED: 'Resolved',
|
||||
RELEASED: 'Released',
|
||||
CLOSED: 'Closed',
|
||||
}
|
||||
|
||||
export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
const [opened, { open, close }] = useDisclosure(false)
|
||||
const [selectedError, setSelectedError] = useState<any>(null)
|
||||
@@ -41,54 +63,62 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
open()
|
||||
}
|
||||
|
||||
const getSeverityColor = (sev: string) => {
|
||||
switch (sev?.toUpperCase()) {
|
||||
case 'OPEN': return 'red'
|
||||
case 'IN_PROGRESS': return 'orange'
|
||||
case 'ON_HOLD': return 'yellow'
|
||||
default: return 'gray'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper withBorder radius="2xl" className="glass overflow-hidden">
|
||||
<Box p="xl" style={{ borderBottom: '1px solid rgba(255, 255, 255, 0.08)' }}>
|
||||
<Paper withBorder radius="2xl" className="glass" style={{ overflow: 'hidden' }}>
|
||||
<Box p="lg" style={{ borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
<Group justify="space-between">
|
||||
<Group gap="sm">
|
||||
<ThemeIcon variant="light" color="red" size="lg" radius="md">
|
||||
<TbBug size={20} />
|
||||
</ThemeIcon>
|
||||
<Text fw={700}>LATEST ERROR REPORTS</Text>
|
||||
<Stack gap={0}>
|
||||
<Text fw={700} size="sm">Latest Error Reports</Text>
|
||||
<Text size="xs" c="dimmed">Most recent open bugs</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<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>
|
||||
<Tooltip label="View all reports" withArrow>
|
||||
<Button
|
||||
component={Link}
|
||||
to={appId ? `/apps/${appId}/errors` : '/bug-reports'}
|
||||
variant="subtle"
|
||||
size="compact-sm"
|
||||
color="blue"
|
||||
rightSection={<TbExternalLink size={14} />}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<ScrollArea>
|
||||
<Table verticalSpacing="md" highlightOnHover className="data-table">
|
||||
<Table.Thead bg="rgba(0,0,0,0.1)">
|
||||
<Table verticalSpacing="sm" highlightOnHover className="data-table">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th px="xl">Error Message</Table.Th>
|
||||
<Table.Th px="lg">Error Description</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.Th>Version</Table.Th>
|
||||
<Table.Th>Reported</Table.Th>
|
||||
<Table.Th pr="lg">Status</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{isLoading ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5} align="center" py="xl">
|
||||
Loading errors...
|
||||
<Table.Td colSpan={5}>
|
||||
<Group justify="center" py="xl">
|
||||
<Loader size="sm" type="dots" />
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : bugs.length === 0 ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5} align="center" py="xl">
|
||||
No errors found.
|
||||
<Table.Td colSpan={5}>
|
||||
<Stack align="center" gap="xs" py="xl">
|
||||
<TbBug size={32} style={{ opacity: 0.25 }} />
|
||||
<Text size="sm" c="dimmed">No error reports found.</Text>
|
||||
</Stack>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : bugs.map((error: any) => (
|
||||
@@ -97,24 +127,34 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
onClick={() => handleRowClick(error)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Table.Td px="xl">
|
||||
<Table.Td px="lg">
|
||||
<Text size="sm" fw={600} lineClamp={1}>{error.description}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge variant="dot" color="brand-blue" radius="sm">{error.user?.name || error.userId || 'System'}</Badge>
|
||||
<Badge variant="light" color="brand-blue" size="sm">
|
||||
{error.user?.name || error.userId || 'System'}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="xs" fw={700} c="dimmed">{error.affectedVersion || 'N/A'}</Text>
|
||||
<Badge variant="light" color="gray" size="sm">
|
||||
v{error.affectedVersion || 'N/A'}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap={6}>
|
||||
<Group gap={4}>
|
||||
<TbHistory size={12} color="gray" />
|
||||
<Text size="xs" c="dimmed">{new Date(error.createdAt).toLocaleString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{dayjs(error.createdAt).format('D MMM YYYY, HH:mm')}
|
||||
</Text>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td pr="xl">
|
||||
<Badge color={getSeverityColor(error.status)} variant="light" size="sm">
|
||||
{(error.status || '').toUpperCase()}
|
||||
<Table.Td pr="lg">
|
||||
<Badge
|
||||
color={STATUS_COLOR[error.status?.toUpperCase()] ?? 'gray'}
|
||||
variant="light"
|
||||
size="sm"
|
||||
>
|
||||
{STATUS_LABEL[error.status?.toUpperCase()] ?? error.status}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
@@ -131,37 +171,68 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
size="md"
|
||||
title={
|
||||
<Group gap="xs">
|
||||
<TbMessageReport color="#ef4444" size={24} />
|
||||
<Title order={4}>Error Investigation</Title>
|
||||
<TbMessageReport color="#ef4444" size={22} />
|
||||
<Title order={4}>Error Detail</Title>
|
||||
</Group>
|
||||
}
|
||||
styles={{
|
||||
header: { padding: '24px', borderBottom: '1px solid var(--mantine-color-default-border)' },
|
||||
header: { padding: '20px 24px', borderBottom: '1px solid var(--mantine-color-default-border)' },
|
||||
}}
|
||||
>
|
||||
{selectedError && (
|
||||
<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.description}</Text>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Description</Text>
|
||||
<Text fw={600} size="sm">{selectedError.description}</Text>
|
||||
</Box>
|
||||
|
||||
<SimpleGrid cols={2} spacing="lg">
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4}>SOURCE</Text>
|
||||
<Text fw={600}>{selectedError.source}</Text>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Status</Text>
|
||||
<Badge
|
||||
color={STATUS_COLOR[selectedError.status?.toUpperCase()] ?? 'gray'}
|
||||
variant="light"
|
||||
size="sm"
|
||||
>
|
||||
{STATUS_LABEL[selectedError.status?.toUpperCase()] ?? selectedError.status}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4}>APP VERSION</Text>
|
||||
<Badge variant="outline">{selectedError.affectedVersion || 'N/A'}</Badge>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Source</Text>
|
||||
<Badge variant="light" color="gray" size="sm">{selectedError.source}</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">App Version</Text>
|
||||
<Badge variant="light" color="gray" size="sm">v{selectedError.affectedVersion || 'N/A'}</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Reported</Text>
|
||||
<Text size="sm" fw={500}>{dayjs(selectedError.createdAt).format('D MMM YYYY, HH:mm')}</Text>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
|
||||
{selectedError.device && (
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Device</Text>
|
||||
<Text size="sm">{selectedError.device} · {selectedError.os}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{selectedError.feedBack && (
|
||||
<>
|
||||
<Divider opacity={0.1} />
|
||||
<Box>
|
||||
<Text size="xs" fw={700} c="dimmed" mb={4} tt="uppercase">Developer Feedback</Text>
|
||||
<Text size="sm" style={{ whiteSpace: 'pre-wrap' }}>{selectedError.feedBack}</Text>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider opacity={0.1} />
|
||||
|
||||
<Box>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text size="xs" fw={700} c="dimmed">STACK TRACE</Text>
|
||||
<Text size="xs" fw={700} c="dimmed" tt="uppercase">Stack Trace</Text>
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
@@ -172,8 +243,12 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
</Button>
|
||||
</Group>
|
||||
{showStackTrace && (
|
||||
<Code block color="red" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6, border: '1px solid var(--mantine-color-default-border)' }}>
|
||||
{selectedError.stackTrace}
|
||||
<Code
|
||||
block
|
||||
color="red"
|
||||
style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6, fontSize: 11, border: '1px solid var(--mantine-color-default-border)' }}
|
||||
>
|
||||
{selectedError.stackTrace || '(no stack trace)'}
|
||||
</Code>
|
||||
)}
|
||||
</Box>
|
||||
@@ -183,5 +258,3 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
import { SimpleGrid, ThemeIcon } from '@mantine/core'
|
||||
|
||||
Reference in New Issue
Block a user