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 { TbTimeline, TbChartBar, TbArrowUpRight } from 'react-icons/tb'
const activityData = [
{ date: 'Mar 26', logs: 1200 },
{ date: 'Mar 27', logs: 1900 },
{ 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 },
]
interface ChartProps {
data?: any[]
isLoading?: boolean
}
const villageComparisonData = [
{ village: 'Sukatani', activity: 4500 },
{ village: 'Sukamaju', activity: 3800 },
{ village: 'Bojong Gede', activity: 3200 },
{ village: 'Beji', activity: 2800 },
{ village: 'Tapos', activity: 2400 },
]
export function VillageActivityLineChart() {
export function VillageActivityLineChart({ data = [], isLoading }: ChartProps) {
const theme = useMantineTheme()
return (
@@ -46,14 +33,14 @@ export function VillageActivityLineChart() {
</Box>
</Group>
<Badge variant="light" color="blue" size="sm" rightSection={<TbArrowUpRight size={12} />}>
Growing
{isLoading ? '...' : 'Live'}
</Badge>
</Group>
<Box h={300} mt="lg">
<LineChart
h={300}
data={activityData}
data={data}
dataKey="date"
series={[{ name: 'logs', color: '#2563EB' }]}
curveType="monotone"
@@ -76,7 +63,7 @@ export function VillageActivityLineChart() {
)
}
export function VillageComparisonBarChart() {
export function VillageComparisonBarChart({ data = [], isLoading }: ChartProps) {
const theme = useMantineTheme()
return (
@@ -89,7 +76,7 @@ export function VillageComparisonBarChart() {
</ThemeIcon>
<Box>
<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>
</Group>
</Group>
@@ -97,7 +84,7 @@ export function VillageComparisonBarChart() {
<Box h={300} mt="lg">
<BarChart
h={300}
data={villageComparisonData}
data={data}
dataKey="village"
series={[{ name: 'activity', color: 'indigo.6' }]}
withTooltip
@@ -112,7 +99,6 @@ export function VillageComparisonBarChart() {
}
}}
>
{/* Custom SVG Gradient definitions for Premium SaaS look */}
<defs>
<linearGradient id="barGradient" x1="0" y1="0" x2="0" y2="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)}`,
getLogsAllVillages: (page: number, search: string) =>
`${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,
Switch,
Badge,
Textarea
Textarea,
Skeleton
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router'
import useSWR from 'swr'
import {
TbActivity,
TbAlertTriangle,
@@ -24,125 +26,131 @@ import {
TbRefresh,
TbVersions
} from 'react-icons/tb'
import { API_URLS } from '../config/api'
export const Route = createFileRoute('/apps/$appId/')({
component: AppOverviewPage,
})
const fetcher = (url: string) => fetch(url).then((res) => res.json())
function AppOverviewPage() {
const { appId } = useParams({ from: '/apps/$appId/' })
const navigate = useNavigate()
const isDesaPlus = appId === 'desa-plus'
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 (
<>
<Modal opened={versionModalOpened} onClose={closeVersionModal} title="Update Version Information" radius="md">
<Stack gap="md">
<TextInput label="Active Version" defaultValue="v1.2.0" />
<TextInput label="Minimum Version" defaultValue="v1.0.0" />
<TextInput label="Active Version" defaultValue={grid?.version?.mobile_latest_version || 'v1.2.0'} />
<TextInput label="Minimum Version" defaultValue={grid?.version?.mobile_minimum_version || 'v1.0.0'} />
<Textarea
label="Update Message"
placeholder="Enter release notes or update message..."
defaultValue={grid?.version?.mobile_message_update || ''}
minRows={3}
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>
</Stack>
</Modal>
<Stack gap="xl">
{/* 🔝 HEADER SECTION */}
{/* <Paper withBorder p="lg" radius="2xl" className="glass"> */}
<Group justify="space-between">
<Stack gap={0}>
<Title order={3}>Overview</Title>
<Text size="sm" c="dimmed">Last updated: Just now</Text>
</Stack>
<Group justify="space-between">
<Stack gap={0}>
<Title order={3}>Overview</Title>
<Text size="sm" c="dimmed">Detailed metrics for {isDesaPlus ? 'Desa+' : appId}</Text>
</Stack>
<Group gap="md">
{/* <Select
placeholder="Date Range"
data={['Today', '7 Days', '30 Days']}
defaultValue="Today"
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 gap="md">
<ActionIcon variant="light" color="brand-blue" size="lg" radius="md" onClick={handleRefresh}>
<TbRefresh size={20} />
</ActionIcon>
</Group>
</Group>
</Group>
{/* </Paper> */}
{/* 📊 1. SUMMARY CARDS */}
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
<SummaryCard
title="Active Version"
value="v1.2.0"
icon={TbVersions}
color="brand-blue"
onClick={openVersionModal}
>
<Group justify="space-between" mt="md">
<Stack gap={0}>
<Text size="xs" c="dimmed">Min. Version</Text>
<Text size="sm" fw={600}>v1.0.0</Text>
</Stack>
<Stack gap={0} align="flex-end">
<Text size="xs" c="dimmed">Maintenance</Text>
<Badge size="sm" color="gray" variant="light">False</Badge>
</Stack>
</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>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
<SummaryCard
title="Active Version"
value={gridLoading ? '...' : (grid?.version?.mobile_latest_version || 'N/A')}
icon={TbVersions}
color="brand-blue"
onClick={openVersionModal}
>
<Group justify="space-between" mt="md">
<Stack gap={0}>
<Text size="xs" c="dimmed">Min. Version</Text>
<Text size="sm" fw={600}>{grid?.version?.mobile_minimum_version || '-'}</Text>
</Stack>
<Stack gap={0} align="flex-end">
<Text size="xs" c="dimmed">Maintenance</Text>
<Badge size="sm" color={grid?.version?.mobile_maintenance === 'true' ? 'red' : 'gray'} variant="light">
{grid?.version?.mobile_maintenance?.toUpperCase() || 'FALSE'}
</Badge>
</Stack>
</Group>
</SummaryCard>
{/* 📈 📊 2 & 3. CHARTS GRID */}
<SimpleGrid cols={{ base: 1, lg: 2 }} spacing="lg">
<VillageActivityLineChart />
<VillageComparisonBarChart />
</SimpleGrid>
<SummaryCard
title="Total Activity Today"
value={gridLoading ? '...' : (grid?.activity?.today?.toLocaleString() || '0')}
icon={TbActivity}
color="teal"
trend={grid?.activity?.increase ? { value: `${grid.activity.increase}%`, positive: grid.activity.increase > 0 } : undefined}
/>
{/* 🐞 4. LATEST ERROR REPORTS */}
<ErrorDataTable />
</Stack>
<SummaryCard
title="Total Villages Active"
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>
</>
)
}