feat: tambah peak hours chart di halaman village detail
This commit is contained in:
@@ -33,6 +33,11 @@ export const API_URLS = {
|
|||||||
return `${DESA_PLUS_PROXY}/api/monitoring/log-all-villages?${params}`
|
return `${DESA_PLUS_PROXY}/api/monitoring/log-all-villages?${params}`
|
||||||
},
|
},
|
||||||
getStaleVillages: (days: 7 | 14 | 30 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/stale-villages?days=${days}`,
|
getStaleVillages: (days: 7 | 14 | 30 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/stale-villages?days=${days}`,
|
||||||
|
getPeakHours: (idVillage?: string) => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
if (idVillage) params.set('idVillage', idVillage)
|
||||||
|
return `${DESA_PLUS_PROXY}/api/monitoring/peak-hours?${params}`
|
||||||
|
},
|
||||||
getInactiveUsers: (days: 7 | 14 | 30 = 7, idVillage?: string, page = 1) => {
|
getInactiveUsers: (days: 7 | 14 | 30 = 7, idVillage?: string, page = 1) => {
|
||||||
const params = new URLSearchParams({ days: String(days), page: String(page) })
|
const params = new URLSearchParams({ days: String(days), page: String(page) })
|
||||||
if (idVillage) params.set('idVillage', idVillage)
|
if (idVillage) params.set('idVillage', idVillage)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AreaChart } from '@mantine/charts'
|
import { AreaChart, BarChart } from '@mantine/charts'
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
@@ -194,6 +194,73 @@ function ActivityChart({ villageId }: { villageId: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Peak Hours Chart ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function PeakHoursChart({ villageId }: { villageId: string }) {
|
||||||
|
const { data: response, isLoading } = useSWR(API_URLS.getPeakHours(villageId), fetcher)
|
||||||
|
const hours: { hour: number; label: string; count: number }[] = response?.data?.hours || []
|
||||||
|
const peak: { label: string; count: number } | null = response?.data?.peak || null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper withBorder radius="xl" p="lg">
|
||||||
|
<Group justify="space-between" mb="lg" wrap="wrap" gap="sm">
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon size={28} radius="md" variant="light" color="violet">
|
||||||
|
<TbClock size={14} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text fw={700} size="sm">Peak Activity Hours</Text>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
{peak && peak.count > 0
|
||||||
|
? `Busiest hour: ${peak.label} (${peak.count.toLocaleString()} activities)`
|
||||||
|
: 'No activity data'}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<Stack h={200} align="center" justify="center">
|
||||||
|
<Loader type="dots" />
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<BarChart
|
||||||
|
h={200}
|
||||||
|
data={hours}
|
||||||
|
dataKey="label"
|
||||||
|
series={[{ name: 'count', color: 'violet.5' }]}
|
||||||
|
withTooltip
|
||||||
|
withXAxis
|
||||||
|
withYAxis={false}
|
||||||
|
tickLine="none"
|
||||||
|
gridAxis="none"
|
||||||
|
barProps={{ radius: 4 }}
|
||||||
|
tooltipProps={{
|
||||||
|
content: ({ active, payload, label }: any) => {
|
||||||
|
if (!active || !payload?.length) return null
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#1A1B1E',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #373A40',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: '12px', fontWeight: 600, color: '#C1C2C5', marginBottom: '4px' }}>{label}</div>
|
||||||
|
<div style={{ fontSize: '11px', color: '#9775FA' }}>
|
||||||
|
Activities: <span style={{ fontWeight: 700 }}>{payload[0].value}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Recent Activity Logs ──────────────────────────────────────────────────────
|
// ── Recent Activity Logs ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
function RecentVillageLogs({ villageId }: { villageId: string }) {
|
function RecentVillageLogs({ villageId }: { villageId: string }) {
|
||||||
@@ -563,6 +630,9 @@ function VillageDetailPage() {
|
|||||||
{/* ── Activity Chart ── */}
|
{/* ── Activity Chart ── */}
|
||||||
<ActivityChart villageId={villageId} />
|
<ActivityChart villageId={villageId} />
|
||||||
|
|
||||||
|
{/* ── Peak Hours Chart ── */}
|
||||||
|
<PeakHoursChart villageId={villageId} />
|
||||||
|
|
||||||
{/* ── Recent Logs + System Info ── */}
|
{/* ── Recent Logs + System Info ── */}
|
||||||
<Grid gutter="md" align="flex-start">
|
<Grid gutter="md" align="flex-start">
|
||||||
<Grid.Col span={{ base: 12, md: 8 }}>
|
<Grid.Col span={{ base: 12, md: 8 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user