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
112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
import { DashboardLayout } from '@/frontend/components/DashboardLayout'
|
|
import { APP_CONFIGS } from '@/frontend/config/appMenus'
|
|
import {
|
|
Badge,
|
|
Box,
|
|
Container,
|
|
Divider,
|
|
Group,
|
|
Skeleton,
|
|
Stack,
|
|
Text,
|
|
Title,
|
|
} from '@mantine/core'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { createFileRoute, Outlet, useParams } from '@tanstack/react-router'
|
|
import { TbAlertTriangle, TbTools } from 'react-icons/tb'
|
|
|
|
export const Route = createFileRoute('/apps/$appId')({
|
|
component: AppDetailLayout,
|
|
})
|
|
|
|
const STATUS_COLOR: Record<string, string> = {
|
|
active: 'teal',
|
|
warning: 'orange',
|
|
error: 'red',
|
|
}
|
|
const STATUS_LABEL: Record<string, string> = {
|
|
active: 'Active',
|
|
warning: 'Warning',
|
|
error: 'Error',
|
|
}
|
|
|
|
function AppDetailLayout() {
|
|
const { appId } = useParams({ from: '/apps/$appId' })
|
|
|
|
const { data: appData, isLoading } = useQuery({
|
|
queryKey: ['apps', appId],
|
|
queryFn: () => fetch(`/api/apps/${appId}`).then((r) => r.json()),
|
|
staleTime: 30_000,
|
|
})
|
|
|
|
const configName = APP_CONFIGS[appId]?.name
|
|
const displayName = appData?.name ?? configName ?? appId
|
|
|
|
const statusKey = appData?.maintenance ? 'maintenance' : (appData?.status ?? 'active')
|
|
const statusColor = appData?.maintenance ? 'gray' : (STATUS_COLOR[appData?.status] ?? 'gray')
|
|
const statusLabel = appData?.maintenance ? 'Maintenance' : (STATUS_LABEL[appData?.status] ?? appData?.status)
|
|
|
|
return (
|
|
<DashboardLayout>
|
|
<Container size="xl" py="lg">
|
|
<Stack gap="md">
|
|
<Group justify="space-between" align="flex-start">
|
|
<Stack gap={6}>
|
|
<Group gap="sm" align="center">
|
|
{isLoading ? (
|
|
<Skeleton height={36} width={180} radius="md" />
|
|
) : (
|
|
<Title order={2} className="gradient-text">{displayName}</Title>
|
|
)}
|
|
{!isLoading && appData && (
|
|
<Badge color={statusColor} variant="dot" size="md">
|
|
{statusLabel}
|
|
</Badge>
|
|
)}
|
|
</Group>
|
|
|
|
<Group gap="xs" align="center">
|
|
<Text size="xs" c="dimmed" fw={500} style={{ fontFamily: 'monospace' }}>
|
|
{appId}
|
|
</Text>
|
|
{isLoading ? (
|
|
<Skeleton height={20} width={60} radius="xl" />
|
|
) : (
|
|
<>
|
|
{(appData?.errors ?? 0) > 0 && (
|
|
<Badge
|
|
variant="light"
|
|
color="red"
|
|
size="sm"
|
|
leftSection={<TbAlertTriangle size={10} />}
|
|
>
|
|
{appData.errors} open {appData.errors === 1 ? 'error' : 'errors'}
|
|
</Badge>
|
|
)}
|
|
{appData?.maintenance && (
|
|
<Badge
|
|
variant="light"
|
|
color="orange"
|
|
size="sm"
|
|
leftSection={<TbTools size={10} />}
|
|
>
|
|
Maintenance mode
|
|
</Badge>
|
|
)}
|
|
</>
|
|
)}
|
|
</Group>
|
|
</Stack>
|
|
</Group>
|
|
|
|
<Divider />
|
|
|
|
<Box>
|
|
<Outlet />
|
|
</Box>
|
|
</Stack>
|
|
</Container>
|
|
</DashboardLayout>
|
|
)
|
|
}
|