feat: add status and role filter on users page
This commit is contained in:
@@ -222,6 +222,8 @@ function UsersIndexPage() {
|
|||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 400)
|
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 isDesaPlus = appId === 'desa-plus'
|
||||||
const apiUrl = isDesaPlus ? API_URLS.getUsers(page, searchQuery) : null
|
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 { data: response, error, isLoading, mutate } = useSWR(apiUrl, fetcher)
|
||||||
const users: APIUser[] = response?.data?.user || []
|
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(() => {
|
useEffect(() => {
|
||||||
if (debouncedSearch.length >= 3 || debouncedSearch.length === 0) {
|
if (debouncedSearch.length >= 3 || debouncedSearch.length === 0) {
|
||||||
setSearchQuery(debouncedSearch)
|
setSearchQuery(debouncedSearch)
|
||||||
@@ -562,23 +573,50 @@ function UsersIndexPage() {
|
|||||||
|
|
||||||
{/* Search / Filter */}
|
{/* Search / Filter */}
|
||||||
<Paper withBorder p="md" className="glass">
|
<Paper withBorder p="md" className="glass">
|
||||||
<TextInput
|
<Group gap="sm" align="flex-end" wrap="nowrap">
|
||||||
placeholder="Search name, NIK, or email... (min. 3 characters)"
|
<TextInput
|
||||||
leftSection={<TbSearch size={16} />}
|
style={{ flex: 1 }}
|
||||||
size="sm"
|
placeholder="Search name, NIK, or email... (min. 3 characters)"
|
||||||
rightSection={
|
leftSection={<TbSearch size={16} />}
|
||||||
search ? (
|
size="sm"
|
||||||
<Tooltip label="Clear search" withArrow>
|
rightSection={
|
||||||
<ActionIcon variant="transparent" color="gray" onClick={handleClearSearch} size="sm">
|
search ? (
|
||||||
<TbX size={16} />
|
<Tooltip label="Clear search" withArrow>
|
||||||
</ActionIcon>
|
<ActionIcon variant="transparent" color="gray" onClick={handleClearSearch} size="sm">
|
||||||
</Tooltip>
|
<TbX size={16} />
|
||||||
) : null
|
</ActionIcon>
|
||||||
}
|
</Tooltip>
|
||||||
value={search}
|
) : null
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
}
|
||||||
radius="md"
|
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>
|
</Paper>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -592,12 +630,12 @@ function UsersIndexPage() {
|
|||||||
<Text size="sm" c="dimmed">Failed to load users from the API.</Text>
|
<Text size="sm" c="dimmed">Failed to load users from the API.</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
) : users.length === 0 ? (
|
) : filteredUsers.length === 0 ? (
|
||||||
<Paper withBorder radius="2xl" className="glass" p="md">
|
<Paper withBorder radius="2xl" className="glass" p="md">
|
||||||
<Stack align="center" gap="xs" py="xl">
|
<Stack align="center" gap="xs" py="xl">
|
||||||
<TbUsers size={32} style={{ opacity: 0.25 }} />
|
<TbUsers size={32} style={{ opacity: 0.25 }} />
|
||||||
<Text size="sm" c="dimmed">
|
<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>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -626,7 +664,7 @@ function UsersIndexPage() {
|
|||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{users.map((user) => (
|
{filteredUsers.map((user) => (
|
||||||
<Table.Tr
|
<Table.Tr
|
||||||
key={user.id}
|
key={user.id}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user