feat: tambah stale villages alert card di halaman overview desa-plus

This commit is contained in:
2026-05-28 14:14:40 +08:00
parent 3c188e66d2
commit 2e64c1c2a6
2 changed files with 67 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ export const API_URLS = {
if (dateTo) params.set('dateTo', dateTo)
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}`,
getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`,
getDailyActivity: (range: 7 | 30 | 90 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/daily-activity?range=${range}`,
getComparisonActivity: (range: 7 | 30 | 90 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/comparison-activity?range=${range}`,

View File

@@ -4,10 +4,15 @@ import { SummaryCard } from '@/frontend/components/SummaryCard'
import { useSession } from '@/frontend/hooks/useAuth'
import {
ActionIcon,
Anchor,
Badge,
Button,
Collapse,
Divider,
Group,
Modal,
Paper,
SegmentedControl,
SimpleGrid,
Stack,
Switch,
@@ -25,6 +30,8 @@ import {
TbActivity,
TbAlertTriangle,
TbBuildingCommunity,
TbChevronDown,
TbChevronUp,
TbRefresh,
TbVersions,
} from 'react-icons/tb'
@@ -54,12 +61,15 @@ function AppOverviewPage() {
const [dailyRange, setDailyRange] = useState<7 | 30 | 90>(7)
const [comparisonRange, setComparisonRange] = useState<7 | 30 | 90>(7)
const [staleDays, setStaleDays] = useState<7 | 14 | 30>(7)
const [staleExpanded, { toggle: toggleStale }] = useDisclosure(false)
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(dailyRange) : null, fetcher)
const { data: comparisonRes, isLoading: comparisonLoading, mutate: mutateComparison } = useSWR(isDesaPlus ? API_URLS.getComparisonActivity(comparisonRange) : null, fetcher)
const { data: appData, isLoading: appLoading } = useSWR(`/api/apps/${appId}`, fetcher)
const { data: staleRes } = useSWR(isDesaPlus ? API_URLS.getStaleVillages(staleDays) : null, fetcher)
const grid = gridRes?.data
const dailyData = dailyRes?.data || []
@@ -248,6 +258,62 @@ function AppOverviewPage() {
/>
</SimpleGrid>
{isDesaPlus && staleRes?.data?.count > 0 && (
<Paper
withBorder
radius="xl"
className="glass"
p="md"
style={{ borderColor: 'var(--mantine-color-orange-7)' }}
>
<Group justify="space-between" wrap="nowrap">
<Group gap="sm" wrap="nowrap">
<TbAlertTriangle size={18} color="var(--mantine-color-orange-5)" style={{ flexShrink: 0 }} />
<Text fw={700} size="sm" c="orange.4">
{staleRes.data.count} desa tidak ada aktivitas dalam {staleDays} hari terakhir
</Text>
</Group>
<Group gap="xs" wrap="nowrap">
<SegmentedControl
size="xs"
value={String(staleDays)}
onChange={(v) => setStaleDays(Number(v) as 7 | 14 | 30)}
data={[
{ label: '7H', value: '7' },
{ label: '14H', value: '14' },
{ label: '30H', value: '30' },
]}
/>
<ActionIcon variant="subtle" color="gray" size="sm" onClick={toggleStale}>
{staleExpanded ? <TbChevronUp size={15} /> : <TbChevronDown size={15} />}
</ActionIcon>
</Group>
</Group>
<Collapse in={staleExpanded}>
<Divider my="sm" opacity={0.2} />
<Stack gap={6}>
{staleRes.data.villages.map((v: { id: string; name: string; daysSince: number | null }) => (
<Group key={v.id} justify="space-between" wrap="nowrap">
<Anchor
size="sm"
fw={500}
c="dimmed"
onClick={() => navigate({ to: `/apps/${appId}/villages/${v.id}` })}
style={{ cursor: 'pointer' }}
>
{v.name}
</Anchor>
<Text size="xs" c="orange.6" fw={600}>
{v.daysSince === null ? 'Belum pernah ada aktivitas' : `${v.daysSince} hari lalu`}
</Text>
</Group>
))}
</Stack>
</Collapse>
</Paper>
)}
<Group justify="space-between" align="flex-end">
<Stack gap={2}>
<Title order={4}>Analytics</Title>