feat: tambah stale villages alert card di halaman overview desa-plus
This commit is contained in:
@@ -32,6 +32,7 @@ export const API_URLS = {
|
|||||||
if (dateTo) params.set('dateTo', dateTo)
|
if (dateTo) params.set('dateTo', dateTo)
|
||||||
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}`,
|
||||||
getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`,
|
getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`,
|
||||||
getDailyActivity: (range: 7 | 30 | 90 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/daily-activity?range=${range}`,
|
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}`,
|
getComparisonActivity: (range: 7 | 30 | 90 = 7) => `${DESA_PLUS_PROXY}/api/monitoring/comparison-activity?range=${range}`,
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import { SummaryCard } from '@/frontend/components/SummaryCard'
|
|||||||
import { useSession } from '@/frontend/hooks/useAuth'
|
import { useSession } from '@/frontend/hooks/useAuth'
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Anchor,
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
|
Collapse,
|
||||||
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Modal,
|
Modal,
|
||||||
|
Paper,
|
||||||
|
SegmentedControl,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -25,6 +30,8 @@ import {
|
|||||||
TbActivity,
|
TbActivity,
|
||||||
TbAlertTriangle,
|
TbAlertTriangle,
|
||||||
TbBuildingCommunity,
|
TbBuildingCommunity,
|
||||||
|
TbChevronDown,
|
||||||
|
TbChevronUp,
|
||||||
TbRefresh,
|
TbRefresh,
|
||||||
TbVersions,
|
TbVersions,
|
||||||
} from 'react-icons/tb'
|
} from 'react-icons/tb'
|
||||||
@@ -54,12 +61,15 @@ function AppOverviewPage() {
|
|||||||
|
|
||||||
const [dailyRange, setDailyRange] = useState<7 | 30 | 90>(7)
|
const [dailyRange, setDailyRange] = useState<7 | 30 | 90>(7)
|
||||||
const [comparisonRange, setComparisonRange] = 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: 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: 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: 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: appData, isLoading: appLoading } = useSWR(`/api/apps/${appId}`, fetcher)
|
||||||
|
const { data: staleRes } = useSWR(isDesaPlus ? API_URLS.getStaleVillages(staleDays) : null, fetcher)
|
||||||
|
|
||||||
const grid = gridRes?.data
|
const grid = gridRes?.data
|
||||||
const dailyData = dailyRes?.data || []
|
const dailyData = dailyRes?.data || []
|
||||||
@@ -248,6 +258,62 @@ function AppOverviewPage() {
|
|||||||
/>
|
/>
|
||||||
</SimpleGrid>
|
</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">
|
<Group justify="space-between" align="flex-end">
|
||||||
<Stack gap={2}>
|
<Stack gap={2}>
|
||||||
<Title order={4}>Analytics</Title>
|
<Title order={4}>Analytics</Title>
|
||||||
|
|||||||
Reference in New Issue
Block a user