upd: connected api monitoring
Deskripsi: - overview page No Issues
This commit is contained in:
@@ -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} />
|
||||||
|
|||||||
@@ -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`,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user