feat: tambah section inactive users di halaman detail desa

This commit is contained in:
2026-05-28 15:14:54 +08:00
parent 733a36bba7
commit a2b3c9bc85

View File

@@ -8,7 +8,9 @@ import {
Group,
Loader,
Modal,
Pagination,
Paper,
ScrollArea,
SegmentedControl,
SimpleGrid,
Stack,
@@ -40,6 +42,7 @@ import {
TbPower,
TbTestPipe,
TbUser,
TbUserOff,
TbUsers,
TbUsersGroup,
TbWifi
@@ -321,6 +324,116 @@ function RecentVillageLogs({ villageId }: { villageId: string }) {
)
}
// ── Inactive Users ────────────────────────────────────────────────────────────
function InactiveVillageUsers({ villageId }: { villageId: string }) {
const [days, setDays] = useState<7 | 14 | 30>(7)
const [page, setPage] = useState(1)
const { data: response, isLoading } = useSWR(
API_URLS.getInactiveUsers(days, villageId, page),
fetcher
)
const users: any[] = response?.data?.users || []
const totalPages: number = response?.data?.totalPage ?? 0
const total: number = response?.data?.total ?? 0
return (
<Paper withBorder radius="xl" p="lg">
<Group justify="space-between" mb="md" wrap="wrap" gap="sm">
<Group gap="xs">
<ThemeIcon size={28} radius="md" variant="light" color="red">
<TbUserOff size={14} />
</ThemeIcon>
<Stack gap={0}>
<Text fw={700} size="sm">Inactive Users</Text>
<Text size="xs" c="dimmed">
{isLoading ? 'Loading...' : `${total} users with no activity in the last ${days} days`}
</Text>
</Stack>
</Group>
<SegmentedControl
size="xs"
value={String(days)}
onChange={(v) => { setDays(Number(v) as 7 | 14 | 30); setPage(1) }}
data={[
{ label: '7D', value: '7' },
{ label: '14D', value: '14' },
{ label: '30D', value: '30' },
]}
/>
</Group>
{isLoading ? (
<Stack h={120} align="center" justify="center">
<Loader type="dots" />
</Stack>
) : users.length === 0 ? (
<Stack align="center" py="md" gap={4}>
<TbUsers size={28} style={{ opacity: 0.25 }} />
<Text size="sm" c="dimmed">No inactive users in this period.</Text>
</Stack>
) : (
<Stack gap="md">
<ScrollArea>
<Table verticalSpacing="xs" className="data-table">
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Role</Table.Th>
<Table.Th>Group / Position</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Last Activity</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{users.map((u: any) => (
<Table.Tr key={u.id}>
<Table.Td>
<Stack gap={0}>
<Text size="sm" fw={600}>{u.name}</Text>
<Text size="xs" c="dimmed">{u.email}</Text>
</Stack>
</Table.Td>
<Table.Td>
<Badge variant="light" color="brand-blue" size="sm" radius="sm">
{u.role}
</Badge>
</Table.Td>
<Table.Td>
<Text size="xs">{u.group}{u.position ? ` · ${u.position}` : ''}</Text>
</Table.Td>
<Table.Td>
<Badge variant="dot" color={u.isActive ? 'teal' : 'red'} size="sm">
{u.isActive ? 'Active' : 'Inactive'}
</Badge>
</Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
{u.daysSince === null ? (
<Text size="xs" c="dimmed">Never</Text>
) : (
<Text size="xs" fw={600} c={u.daysSince > 30 ? 'red.5' : u.daysSince > 7 ? 'yellow.5' : 'dimmed'}>
{u.daysSince}d ago
</Text>
)}
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea>
{totalPages > 1 && (
<Group justify="center">
<Pagination value={page} onChange={setPage} total={totalPages} size="sm" radius="md" withEdges={false} siblings={1} />
</Group>
)}
</Stack>
)}
</Paper>
)
}
// ── Main Page ─────────────────────────────────────────────────────────────────
function VillageDetailPage() {
@@ -671,6 +784,9 @@ function VillageDetailPage() {
</Grid.Col>
</Grid>
{/* ── Inactive Users ── */}
<InactiveVillageUsers villageId={villageId} />
{/* ── Confirmation Modal ── */}
<Modal
opened={confirmModalOpened}