amalia/28-mei-26 #27
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user