amalia/22-mei-26 #25

Merged
amaliadwiy merged 13 commits from amalia/22-mei-26 into main 2026-05-22 17:40:31 +08:00
Showing only changes of commit ed9f59f404 - Show all commits

View File

@@ -222,6 +222,8 @@ function UsersIndexPage() {
const [search, setSearch] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 400)
const [filterStatus, setFilterStatus] = useState<string | null>(null)
const [filterRole, setFilterRole] = useState<string | null>(null)
const isDesaPlus = appId === 'desa-plus'
const apiUrl = isDesaPlus ? API_URLS.getUsers(page, searchQuery) : null
@@ -229,6 +231,15 @@ function UsersIndexPage() {
const { data: response, error, isLoading, mutate } = useSWR(apiUrl, fetcher)
const users: APIUser[] = response?.data?.user || []
const filteredUsers = users.filter((user) => {
if (filterStatus === 'active' && !user.isActive) return false
if (filterStatus === 'inactive' && user.isActive) return false
if (filterRole && user.role !== filterRole) return false
return true
})
const roleFilterOptions = Array.from(new Set(users.map((u) => u.role))).map((r) => ({ value: r, label: r }))
useEffect(() => {
if (debouncedSearch.length >= 3 || debouncedSearch.length === 0) {
setSearchQuery(debouncedSearch)
@@ -562,23 +573,50 @@ function UsersIndexPage() {
{/* Search / Filter */}
<Paper withBorder p="md" className="glass">
<TextInput
placeholder="Search name, NIK, or email... (min. 3 characters)"
leftSection={<TbSearch size={16} />}
size="sm"
rightSection={
search ? (
<Tooltip label="Clear search" withArrow>
<ActionIcon variant="transparent" color="gray" onClick={handleClearSearch} size="sm">
<TbX size={16} />
</ActionIcon>
</Tooltip>
) : null
}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
radius="md"
/>
<Group gap="sm" align="flex-end" wrap="nowrap">
<TextInput
style={{ flex: 1 }}
placeholder="Search name, NIK, or email... (min. 3 characters)"
leftSection={<TbSearch size={16} />}
size="sm"
rightSection={
search ? (
<Tooltip label="Clear search" withArrow>
<ActionIcon variant="transparent" color="gray" onClick={handleClearSearch} size="sm">
<TbX size={16} />
</ActionIcon>
</Tooltip>
) : null
}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
radius="md"
/>
<Select
size="sm"
placeholder="Status"
data={[
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
]}
value={filterStatus}
onChange={setFilterStatus}
radius="md"
clearable
w={130}
/>
<Select
size="sm"
placeholder="Role"
data={roleFilterOptions}
value={filterRole}
onChange={setFilterRole}
radius="md"
clearable
w={150}
disabled={roleFilterOptions.length === 0}
/>
</Group>
</Paper>
{isLoading ? (
@@ -592,12 +630,12 @@ function UsersIndexPage() {
<Text size="sm" c="dimmed">Failed to load users from the API.</Text>
</Stack>
</Paper>
) : users.length === 0 ? (
) : filteredUsers.length === 0 ? (
<Paper withBorder radius="2xl" className="glass" p="md">
<Stack align="center" gap="xs" py="xl">
<TbUsers size={32} style={{ opacity: 0.25 }} />
<Text size="sm" c="dimmed">
{searchQuery ? 'No users match your search.' : 'No users found.'}
{searchQuery || filterStatus || filterRole ? 'No users match your filters.' : 'No users found.'}
</Text>
</Stack>
</Paper>
@@ -626,7 +664,7 @@ function UsersIndexPage() {
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{users.map((user) => (
{filteredUsers.map((user) => (
<Table.Tr
key={user.id}
style={{ cursor: 'pointer' }}