diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts index 421aed0..6fa811d 100644 --- a/src/frontend/config/api.ts +++ b/src/frontend/config/api.ts @@ -9,8 +9,15 @@ export const API_URLS = { `${DESA_PLUS_PROXY}/api/monitoring/grid-villages?id=${id}`, graphLogVillages: (id: string, time: string) => `${DESA_PLUS_PROXY}/api/monitoring/graph-log-villages?id=${id}&time=${time}`, - getUsers: (page: number, search: string) => - `${DESA_PLUS_PROXY}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`, + getUsers: (page: number, search: string, isActive?: string, idUserRole?: string, idVillage?: string, orderBy?: string, orderDir?: string) => { + const params = new URLSearchParams({ page: String(page), search }) + if (isActive !== undefined) params.set('isActive', isActive) + if (idUserRole) params.set('idUserRole', idUserRole) + if (idVillage) params.set('idVillage', idVillage) + if (orderBy) params.set('orderBy', orderBy) + if (orderDir) params.set('orderDir', orderDir) + return `${DESA_PLUS_PROXY}/api/monitoring/user?${params}` + }, getLogsAllVillages: (page: number, search: string) => `${DESA_PLUS_PROXY}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`, getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`, diff --git a/src/frontend/routes/apps.$appId.users.index.tsx b/src/frontend/routes/apps.$appId.users.index.tsx index a52c092..c37c43a 100644 --- a/src/frontend/routes/apps.$appId.users.index.tsx +++ b/src/frontend/routes/apps.$appId.users.index.tsx @@ -28,6 +28,9 @@ import { createFileRoute, useParams } from '@tanstack/react-router' import { useEffect, useState } from 'react' import { TbAlertCircle, + TbArrowDown, + TbArrowsSort, + TbArrowUp, TbBriefcase, TbCircleCheck, TbCircleX, @@ -224,22 +227,39 @@ function UsersIndexPage() { const [debouncedSearch] = useDebouncedValue(search, 400) const [filterStatus, setFilterStatus] = useState(null) const [filterRole, setFilterRole] = useState(null) + const [filterVillageSearch, setFilterVillageSearch] = useState('') + const [filterVillageId, setFilterVillageId] = useState(null) + const [sortBy, setSortBy] = useState(null) + const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc') + + const handleSort = (col: string) => { + if (sortBy === col) { + setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')) + } else { + setSortBy(col) + setSortDir('asc') + } + setPage(1) + } const isDesaPlus = appId === 'desa-plus' - const apiUrl = isDesaPlus ? API_URLS.getUsers(page, searchQuery) : null + + const filterStatusParam = filterStatus === 'active' ? 'true' : filterStatus === 'inactive' ? 'false' : undefined + const apiUrl = isDesaPlus + ? API_URLS.getUsers( + page, + searchQuery, + filterStatusParam, + filterRole ?? undefined, + filterVillageId ?? undefined, + sortBy ?? undefined, + sortBy ? sortDir : undefined, + ) + : null 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) @@ -247,6 +267,10 @@ function UsersIndexPage() { } }, [debouncedSearch]) + useEffect(() => { + setPage(1) + }, [filterStatus, filterRole, filterVillageId]) + const handleClearSearch = () => { setSearch('') setSearchQuery('') @@ -291,7 +315,11 @@ function UsersIndexPage() { const targetVillageId = opened ? form.idVillage : editForm.idVillage const targetGroupId = opened ? form.idGroup : editForm.idGroup - const { data: rolesResp } = useSWR(isAnyModalOpened ? API_URLS.listRole() : null, fetcher) + const { data: rolesResp } = useSWR(isDesaPlus ? API_URLS.listRole() : null, fetcher) + const { data: filterVillagesResp } = useSWR( + isDesaPlus && filterVillageSearch.length >= 1 ? API_URLS.getVillages(1, filterVillageSearch) : null, + fetcher + ) const { data: villagesResp } = useSWR( isAnyModalOpened && villageSearch.length >= 1 ? API_URLS.getVillages(1, villageSearch) : null, fetcher @@ -306,6 +334,7 @@ function UsersIndexPage() { ) const rolesOptions = (rolesResp?.data || []).map((r: any) => ({ value: r.id, label: r.name })) + const filterVillagesOptions = (filterVillagesResp?.data || []).map((v: any) => ({ value: v.id, label: v.name })) const villagesOptions = (villagesResp?.data || []).map((v: any) => ({ value: v.id, label: v.name })) const groupsOptions = (groupsResp?.data || []).map((g: any) => ({ value: g.id, label: g.name })) const positionsOptions = (positionsResp?.data || []).map((p: any) => ({ value: p.id, label: p.name })) @@ -573,9 +602,8 @@ function UsersIndexPage() { {/* Search / Filter */} - + } size="sm" @@ -592,31 +620,44 @@ function UsersIndexPage() { onChange={(e) => setSearch(e.currentTarget.value)} radius="md" /> - - + + +