diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts
index 76878f0..78b1235 100644
--- a/src/frontend/config/api.ts
+++ b/src/frontend/config/api.ts
@@ -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}`,
diff --git a/src/frontend/routes/apps.$appId.index.tsx b/src/frontend/routes/apps.$appId.index.tsx
index e5b3b40..b3d98c3 100644
--- a/src/frontend/routes/apps.$appId.index.tsx
+++ b/src/frontend/routes/apps.$appId.index.tsx
@@ -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() {
/>
+ {isDesaPlus && staleRes?.data?.count > 0 && (
+
+
+
+
+
+ {staleRes.data.count} desa tidak ada aktivitas dalam {staleDays} hari terakhir
+
+
+
+ setStaleDays(Number(v) as 7 | 14 | 30)}
+ data={[
+ { label: '7H', value: '7' },
+ { label: '14H', value: '14' },
+ { label: '30H', value: '30' },
+ ]}
+ />
+
+ {staleExpanded ? : }
+
+
+
+
+
+
+
+ {staleRes.data.villages.map((v: { id: string; name: string; daysSince: number | null }) => (
+
+ navigate({ to: `/apps/${appId}/villages/${v.id}` })}
+ style={{ cursor: 'pointer' }}
+ >
+ {v.name}
+
+
+ {v.daysSince === null ? 'Belum pernah ada aktivitas' : `${v.daysSince} hari lalu`}
+
+
+ ))}
+
+
+
+ )}
+
Analytics