feat: tambah section inactive users di halaman detail desa
This commit is contained in:
@@ -8,7 +8,9 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Modal,
|
Modal,
|
||||||
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
|
ScrollArea,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
@@ -40,6 +42,7 @@ import {
|
|||||||
TbPower,
|
TbPower,
|
||||||
TbTestPipe,
|
TbTestPipe,
|
||||||
TbUser,
|
TbUser,
|
||||||
|
TbUserOff,
|
||||||
TbUsers,
|
TbUsers,
|
||||||
TbUsersGroup,
|
TbUsersGroup,
|
||||||
TbWifi
|
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 ─────────────────────────────────────────────────────────────────
|
// ── Main Page ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function VillageDetailPage() {
|
function VillageDetailPage() {
|
||||||
@@ -671,6 +784,9 @@ function VillageDetailPage() {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{/* ── Inactive Users ── */}
|
||||||
|
<InactiveVillageUsers villageId={villageId} />
|
||||||
|
|
||||||
{/* ── Confirmation Modal ── */}
|
{/* ── Confirmation Modal ── */}
|
||||||
<Modal
|
<Modal
|
||||||
opened={confirmModalOpened}
|
opened={confirmModalOpened}
|
||||||
|
|||||||
Reference in New Issue
Block a user