upd: connected api monitoring

Deskripsi:
- overview page

No Issues
This commit is contained in:
2026-04-09 15:21:10 +08:00
parent 3a91bb5b17
commit ba74539542
3 changed files with 110 additions and 113 deletions

View File

@@ -11,25 +11,12 @@ import {
import { LineChart, BarChart } from '@mantine/charts' import { LineChart, BarChart } from '@mantine/charts'
import { TbTimeline, TbChartBar, TbArrowUpRight } from 'react-icons/tb' import { TbTimeline, TbChartBar, TbArrowUpRight } from 'react-icons/tb'
const activityData = [ interface ChartProps {
{ date: 'Mar 26', logs: 1200 }, data?: any[]
{ date: 'Mar 27', logs: 1900 }, isLoading?: boolean
{ date: 'Mar 28', logs: 1540 }, }
{ date: 'Mar 29', logs: 2400 },
{ date: 'Mar 30', logs: 2100 },
{ date: 'Mar 31', logs: 3200 },
{ date: 'Apr 01', logs: 3800 },
]
const villageComparisonData = [ export function VillageActivityLineChart({ data = [], isLoading }: ChartProps) {
{ village: 'Sukatani', activity: 4500 },
{ village: 'Sukamaju', activity: 3800 },
{ village: 'Bojong Gede', activity: 3200 },
{ village: 'Beji', activity: 2800 },
{ village: 'Tapos', activity: 2400 },
]
export function VillageActivityLineChart() {
const theme = useMantineTheme() const theme = useMantineTheme()
return ( return (
@@ -46,14 +33,14 @@ export function VillageActivityLineChart() {
</Box> </Box>
</Group> </Group>
<Badge variant="light" color="blue" size="sm" rightSection={<TbArrowUpRight size={12} />}> <Badge variant="light" color="blue" size="sm" rightSection={<TbArrowUpRight size={12} />}>
Growing {isLoading ? '...' : 'Live'}
</Badge> </Badge>
</Group> </Group>
<Box h={300} mt="lg"> <Box h={300} mt="lg">
<LineChart <LineChart
h={300} h={300}
data={activityData} data={data}
dataKey="date" dataKey="date"
series={[{ name: 'logs', color: '#2563EB' }]} series={[{ name: 'logs', color: '#2563EB' }]}
curveType="monotone" curveType="monotone"
@@ -76,7 +63,7 @@ export function VillageActivityLineChart() {
) )
} }
export function VillageComparisonBarChart() { export function VillageComparisonBarChart({ data = [], isLoading }: ChartProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
return ( return (
@@ -89,7 +76,7 @@ export function VillageComparisonBarChart() {
</ThemeIcon> </ThemeIcon>
<Box> <Box>
<Text fw={700} size="sm">USAGE COMPARISON BETWEEN VILLAGES</Text> <Text fw={700} size="sm">USAGE COMPARISON BETWEEN VILLAGES</Text>
<Text size="xs" c="dimmed">Top 5 most active village deployments</Text> <Text size="xs" c="dimmed">Most active village deployments</Text>
</Box> </Box>
</Group> </Group>
</Group> </Group>
@@ -97,7 +84,7 @@ export function VillageComparisonBarChart() {
<Box h={300} mt="lg"> <Box h={300} mt="lg">
<BarChart <BarChart
h={300} h={300}
data={villageComparisonData} data={data}
dataKey="village" dataKey="village"
series={[{ name: 'activity', color: 'indigo.6' }]} series={[{ name: 'activity', color: 'indigo.6' }]}
withTooltip withTooltip
@@ -112,7 +99,6 @@ export function VillageComparisonBarChart() {
} }
}} }}
> >
{/* Custom SVG Gradient definitions for Premium SaaS look */}
<defs> <defs>
<linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#2563EB" stopOpacity={1} /> <stop offset="0%" stopColor="#2563EB" stopOpacity={1} />

View File

@@ -13,4 +13,7 @@ export const API_URLS = {
`${API_BASE_URL}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`, `${API_BASE_URL}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`,
getLogsAllVillages: (page: number, search: string) => getLogsAllVillages: (page: number, search: string) =>
`${API_BASE_URL}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`, `${API_BASE_URL}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`,
getGridOverview: () => `${API_BASE_URL}/api/monitoring/grid-overview`,
getDailyActivity: () => `${API_BASE_URL}/api/monitoring/daily-activity`,
getComparisonActivity: () => `${API_BASE_URL}/api/monitoring/comparison-activity`,
} }

View File

@@ -13,10 +13,12 @@ import {
TextInput, TextInput,
Switch, Switch,
Badge, Badge,
Textarea Textarea,
Skeleton
} from '@mantine/core' } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks' import { useDisclosure } from '@mantine/hooks'
import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router' import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router'
import useSWR from 'swr'
import { import {
TbActivity, TbActivity,
TbAlertTriangle, TbAlertTriangle,
@@ -24,125 +26,131 @@ import {
TbRefresh, TbRefresh,
TbVersions TbVersions
} from 'react-icons/tb' } from 'react-icons/tb'
import { API_URLS } from '../config/api'
export const Route = createFileRoute('/apps/$appId/')({ export const Route = createFileRoute('/apps/$appId/')({
component: AppOverviewPage, component: AppOverviewPage,
}) })
const fetcher = (url: string) => fetch(url).then((res) => res.json())
function AppOverviewPage() { function AppOverviewPage() {
const { appId } = useParams({ from: '/apps/$appId/' }) const { appId } = useParams({ from: '/apps/$appId/' })
const navigate = useNavigate() const navigate = useNavigate()
const isDesaPlus = appId === 'desa-plus' const isDesaPlus = appId === 'desa-plus'
const [versionModalOpened, { open: openVersionModal, close: closeVersionModal }] = useDisclosure(false) const [versionModalOpened, { open: openVersionModal, close: closeVersionModal }] = useDisclosure(false)
// Data Fetching
const { data: gridRes, isLoading: gridLoading, mutate: mutateGrid } = useSWR(isDesaPlus ? API_URLS.getGridOverview() : null, fetcher)
const { data: dailyRes, isLoading: dailyLoading, mutate: mutateDaily } = useSWR(isDesaPlus ? API_URLS.getDailyActivity() : null, fetcher)
const { data: comparisonRes, isLoading: comparisonLoading, mutate: mutateComparison } = useSWR(isDesaPlus ? API_URLS.getComparisonActivity() : null, fetcher)
const grid = gridRes?.data
const dailyData = dailyRes?.data || []
const comparisonData = comparisonRes?.data || []
const handleRefresh = () => {
mutateGrid()
mutateDaily()
mutateComparison()
}
return ( return (
<> <>
<Modal opened={versionModalOpened} onClose={closeVersionModal} title="Update Version Information" radius="md"> <Modal opened={versionModalOpened} onClose={closeVersionModal} title="Update Version Information" radius="md">
<Stack gap="md"> <Stack gap="md">
<TextInput label="Active Version" defaultValue="v1.2.0" /> <TextInput label="Active Version" defaultValue={grid?.version?.mobile_latest_version || 'v1.2.0'} />
<TextInput label="Minimum Version" defaultValue="v1.0.0" /> <TextInput label="Minimum Version" defaultValue={grid?.version?.mobile_minimum_version || 'v1.0.0'} />
<Textarea <Textarea
label="Update Message" label="Update Message"
placeholder="Enter release notes or update message..." placeholder="Enter release notes or update message..."
defaultValue={grid?.version?.mobile_message_update || ''}
minRows={3} minRows={3}
autosize autosize
/> />
<Switch label="Maintenance Mode" description="Enable to put the app in maintenance mode for users." /> <Switch
label="Maintenance Mode"
description="Enable to put the app in maintenance mode for users."
defaultChecked={grid?.version?.mobile_maintenance === 'true'}
/>
<Button fullWidth onClick={closeVersionModal}>Save Changes</Button> <Button fullWidth onClick={closeVersionModal}>Save Changes</Button>
</Stack> </Stack>
</Modal> </Modal>
<Stack gap="xl"> <Stack gap="xl">
{/* 🔝 HEADER SECTION */} <Group justify="space-between">
{/* <Paper withBorder p="lg" radius="2xl" className="glass"> */} <Stack gap={0}>
<Group justify="space-between"> <Title order={3}>Overview</Title>
<Stack gap={0}> <Text size="sm" c="dimmed">Detailed metrics for {isDesaPlus ? 'Desa+' : appId}</Text>
<Title order={3}>Overview</Title> </Stack>
<Text size="sm" c="dimmed">Last updated: Just now</Text>
</Stack>
<Group gap="md"> <Group gap="md">
{/* <Select <ActionIcon variant="light" color="brand-blue" size="lg" radius="md" onClick={handleRefresh}>
placeholder="Date Range" <TbRefresh size={20} />
data={['Today', '7 Days', '30 Days']} </ActionIcon>
defaultValue="Today" </Group>
leftSection={<TbCalendar size={16} />}
radius="md"
w={140}
/> */}
<ActionIcon variant="light" color="brand-blue" size="lg" radius="md">
<TbRefresh size={20} />
</ActionIcon>
{/* <Button
variant="gradient"
gradient={{ from: '#2563EB', to: '#7C3AED' }}
radius="md"
leftSection={<TbFilter size={18} />}
>
Add Filter
</Button> */}
</Group> </Group>
</Group>
{/* </Paper> */}
{/* 📊 1. SUMMARY CARDS */} <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg"> <SummaryCard
<SummaryCard title="Active Version"
title="Active Version" value={gridLoading ? '...' : (grid?.version?.mobile_latest_version || 'N/A')}
value="v1.2.0" icon={TbVersions}
icon={TbVersions} color="brand-blue"
color="brand-blue" onClick={openVersionModal}
onClick={openVersionModal} >
> <Group justify="space-between" mt="md">
<Group justify="space-between" mt="md"> <Stack gap={0}>
<Stack gap={0}> <Text size="xs" c="dimmed">Min. Version</Text>
<Text size="xs" c="dimmed">Min. Version</Text> <Text size="sm" fw={600}>{grid?.version?.mobile_minimum_version || '-'}</Text>
<Text size="sm" fw={600}>v1.0.0</Text> </Stack>
</Stack> <Stack gap={0} align="flex-end">
<Stack gap={0} align="flex-end"> <Text size="xs" c="dimmed">Maintenance</Text>
<Text size="xs" c="dimmed">Maintenance</Text> <Badge size="sm" color={grid?.version?.mobile_maintenance === 'true' ? 'red' : 'gray'} variant="light">
<Badge size="sm" color="gray" variant="light">False</Badge> {grid?.version?.mobile_maintenance?.toUpperCase() || 'FALSE'}
</Stack> </Badge>
</Group> </Stack>
</SummaryCard> </Group>
<SummaryCard </SummaryCard>
title="Total Activity Today"
value="3,842"
icon={TbActivity}
color="teal"
trend={{ value: '14.2%', positive: true }}
/>
<SummaryCard
title="Total Villages Active"
value="138"
icon={TbBuildingCommunity}
color="indigo"
onClick={() => navigate({ to: `/apps/${appId}/villages` })}
>
<Group justify="space-between" mt="md">
<Text size="xs" c="dimmed">Nonactive Villages</Text>
<Badge size="sm" color="red" variant="light">24</Badge>
</Group>
</SummaryCard>
<SummaryCard
title="Errors Today"
value="12"
icon={TbAlertTriangle}
color="red"
isError={true}
trend={{ value: '4.8%', positive: false }}
/>
</SimpleGrid>
{/* 📈 📊 2 & 3. CHARTS GRID */} <SummaryCard
<SimpleGrid cols={{ base: 1, lg: 2 }} spacing="lg"> title="Total Activity Today"
<VillageActivityLineChart /> value={gridLoading ? '...' : (grid?.activity?.today?.toLocaleString() || '0')}
<VillageComparisonBarChart /> icon={TbActivity}
</SimpleGrid> color="teal"
trend={grid?.activity?.increase ? { value: `${grid.activity.increase}%`, positive: grid.activity.increase > 0 } : undefined}
/>
{/* 🐞 4. LATEST ERROR REPORTS */} <SummaryCard
<ErrorDataTable /> title="Total Villages Active"
</Stack> value={gridLoading ? '...' : (grid?.village?.active || '0')}
icon={TbBuildingCommunity}
color="indigo"
onClick={() => navigate({ to: `/apps/${appId}/villages` })}
>
<Group justify="space-between" mt="md">
<Text size="xs" c="dimmed">Nonactive Villages</Text>
<Badge size="sm" color="red" variant="light">{grid?.village?.inactive || 0}</Badge>
</Group>
</SummaryCard>
<SummaryCard
title="Errors Today"
value="12"
icon={TbAlertTriangle}
color="red"
isError={true}
trend={{ value: '4.8%', positive: false }}
/>
</SimpleGrid>
<SimpleGrid cols={{ base: 1, lg: 2 }} spacing="lg">
<VillageActivityLineChart data={dailyData} isLoading={dailyLoading} />
<VillageComparisonBarChart data={comparisonData} isLoading={comparisonLoading} />
</SimpleGrid>
<ErrorDataTable />
</Stack>
</> </>
) )
} }