From 91ad56348f90868c344a74b3cb8da1d09eca53d0 Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 9 Apr 2026 14:27:49 +0800 Subject: [PATCH] upd: connected api monitoring Deskripsi: - list user - tampilan page list user No Issues --- src/frontend/config/api.ts | 2 + src/frontend/config/appMenus.ts | 3 +- .../routes/apps.$appId.users.index.tsx | 297 ++++++++++++++++++ src/frontend/routes/apps.$appId.users.tsx | 9 + 4 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 src/frontend/routes/apps.$appId.users.index.tsx create mode 100644 src/frontend/routes/apps.$appId.users.tsx diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts index 1b719ee..f65a3aa 100644 --- a/src/frontend/config/api.ts +++ b/src/frontend/config/api.ts @@ -9,4 +9,6 @@ export const API_URLS = { `${API_BASE_URL}/api/monitoring/grid-villages?id=${id}`, graphLogVillages: (id: string, time: string) => `${API_BASE_URL}/api/monitoring/graph-log-villages?id=${id}&time=${time}`, + getUsers: (page: number, search: string) => + `${API_BASE_URL}/api/monitoring/user?page=${page}&search=${encodeURIComponent(search)}`, } diff --git a/src/frontend/config/appMenus.ts b/src/frontend/config/appMenus.ts index 2af209b..fb39f1b 100644 --- a/src/frontend/config/appMenus.ts +++ b/src/frontend/config/appMenus.ts @@ -1,5 +1,5 @@ import { IconType } from 'react-icons' -import { TbChartBar, TbHistory, TbAlertTriangle, TbSettings, TbShoppingCart, TbPackage, TbCreditCard, TbBuilding } from 'react-icons/tb' +import { TbAlertTriangle, TbBuilding, TbChartBar, TbCreditCard, TbHistory, TbPackage, TbShoppingCart, TbUsers } from 'react-icons/tb' export interface MenuItem { value: string @@ -23,6 +23,7 @@ export const APP_CONFIGS: Record = { { value: 'logs', label: 'Log Activity', icon: TbHistory, to: '/apps/desa-plus/logs' }, { value: 'errors', label: 'Error Reports', icon: TbAlertTriangle, to: '/apps/desa-plus/errors' }, { value: 'villages', label: 'Villages', icon: TbBuilding, to: '/apps/desa-plus/villages' }, + { value: 'users', label: 'Users', icon: TbUsers, to: '/apps/desa-plus/users' }, ], }, 'e-commerce': { diff --git a/src/frontend/routes/apps.$appId.users.index.tsx b/src/frontend/routes/apps.$appId.users.index.tsx new file mode 100644 index 0000000..1b19141 --- /dev/null +++ b/src/frontend/routes/apps.$appId.users.index.tsx @@ -0,0 +1,297 @@ +import { useState } from 'react' +import useSWR from 'swr' +import { + Badge, + Container, + Group, + Stack, + Text, + Title, + Paper, + Button, + ActionIcon, + TextInput, + Table, + Avatar, + Box, + Pagination, + ThemeIcon, + ScrollArea, + Tooltip, +} from '@mantine/core' +import { useMediaQuery } from '@mantine/hooks' +import { createFileRoute, useParams } from '@tanstack/react-router' +import { + TbPlus, + TbSearch, + TbUsers, + TbX, + TbMail, + TbPhone, + TbId, + TbBriefcase, + TbHome2, + TbCircleCheck, + TbCircleX, +} from 'react-icons/tb' +import { API_URLS } from '../config/api' + +export const Route = createFileRoute('/apps/$appId/users/')({ + component: UsersIndexPage, +}) + +interface APIUser { + id: string + name: string + nik: string + phone: string + email: string + isWithoutOTP: boolean + isActive: boolean + role: string + village: string + group: string + position?: string +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()) + +function UsersIndexPage() { + const { appId } = useParams({ from: '/apps/$appId/users/' }) + const [page, setPage] = useState(1) + const [search, setSearch] = useState('') + const [searchQuery, setSearchQuery] = useState('') + + const isDesaPlus = appId === 'desa-plus' + const apiUrl = isDesaPlus ? API_URLS.getUsers(page, searchQuery) : null + + const { data: response, error, isLoading } = useSWR(apiUrl, fetcher) + const users: APIUser[] = response?.data?.user || [] + + const handleSearchChange = (val: string) => { + setSearch(val) + if (val.length >= 3 || val.length === 0) { + setSearchQuery(val) + setPage(1) + } + } + + const handleClearSearch = () => { + setSearch('') + setSearchQuery('') + setPage(1) + } + + const getRoleColor = (role: string) => { + const r = role.toLowerCase() + if (r.includes('super')) return 'red' + if (r.includes('admin')) return 'brand-blue' + if (r.includes('developer')) return 'violet' + return 'gray' + } + + const isMobile = useMediaQuery('(max-width: 768px)') + + if (!isDesaPlus) { + return ( + + + + User Management + This feature is currently customized for Desa+. Other apps coming soon. + + + ) + } + + return ( + + + + + + + + + + User Management + + + {isLoading ? 'Loading users...' : `${response?.data?.total || 0} users registered in the Desa+ system`} + + + + + + } + size="md" + rightSection={ + search ? ( + + + + ) : null + } + value={search} + onChange={(e) => handleSearchChange(e.currentTarget.value)} + radius="md" + style={{ maxWidth: 500 }} + ml={40} + /> + + + + {isLoading ? ( + + Loading user data... + + ) : error ? ( + + Failed to load data from API. + + ) : users.length === 0 ? ( + + + No users match your criteria. + + ) : ( + + + + + + User & ID + Contact Detail + Organization + Role + Status + + + + {users.map((user) => ( + + + + + {user.name.charAt(0)} + + + {user.name} + + + {user.nik} + + + + + + + + + + + {user.email} + + + + + + {user.phone} + + + + + + + + + + {user.village} + + + + + + {user.group} ยท {user.position || 'Staff'} + + + + + + {user.role} + + + + + + {user.isActive ? ( + + ) : ( + + )} + + {user.isActive ? 'ACTIVE' : 'INACTIVE'} + + + {user.isWithoutOTP && ( + + NO OTP + + )} + + + + ))} + +
+
+
+ )} + + {!isLoading && !error && response?.data?.totalPage > 0 && ( + + + + )} +
+ ) +} diff --git a/src/frontend/routes/apps.$appId.users.tsx b/src/frontend/routes/apps.$appId.users.tsx new file mode 100644 index 0000000..fd6a7d1 --- /dev/null +++ b/src/frontend/routes/apps.$appId.users.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export const Route = createFileRoute('/apps/$appId/users')({ + component: UsersLayout, +}) + +function UsersLayout() { + return +}