diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts index f65a3aa..1cb70e3 100644 --- a/src/frontend/config/api.ts +++ b/src/frontend/config/api.ts @@ -11,4 +11,6 @@ export const API_URLS = { `${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)}`, + getLogsAllVillages: (page: number, search: string) => + `${API_BASE_URL}/api/monitoring/log-all-villages?page=${page}&search=${encodeURIComponent(search)}`, } diff --git a/src/frontend/routes/apps.$appId.logs.tsx b/src/frontend/routes/apps.$appId.logs.tsx index 81b4bcf..16c9cf7 100644 --- a/src/frontend/routes/apps.$appId.logs.tsx +++ b/src/frontend/routes/apps.$appId.logs.tsx @@ -1,6 +1,7 @@ +import { useState } from 'react' +import useSWR from 'swr' import { Badge, - Container, Group, Stack, Text, @@ -8,116 +9,244 @@ import { Paper, Table, TextInput, - Select, ActionIcon, - Tooltip, Avatar, Code, - Button + Button, + Box, + Pagination, + ThemeIcon, + ScrollArea, + Container, } from '@mantine/core' +import { useMediaQuery } from '@mantine/hooks' import { createFileRoute, useParams } from '@tanstack/react-router' -import { TbSearch, TbFilter, TbDownload, TbCalendar } from 'react-icons/tb' +import { + TbSearch, + TbDownload, + TbX, + TbHistory, + TbCalendar, + TbUser, + TbHome2 +} from 'react-icons/tb' +import { API_URLS } from '../config/api' export const Route = createFileRoute('/apps/$appId/logs')({ component: AppLogsPage, }) -const mockLogs = [ - { id: 1, type: 'DOCUMENT', village: 'Sukatani', activity: 'GENERATE_SURAT_DOMISILI', operator: 'Budi Santoso', time: '2 mins ago', status: 'SUCCESS' }, - { id: 2, type: 'FINANCE', village: 'Sukamaju', activity: 'UPLOAD_LAPORAN_REALISASI_Q1', operator: 'Siti Aminah', time: '15 mins ago', status: 'SUCCESS' }, - { id: 3, type: 'SYNC', village: 'Cikini', activity: 'SYNC_DATA_PENDUDUK_SIAK', operator: 'System', time: '1 hour ago', status: 'WARNING' }, - { id: 4, type: 'SECURITY', village: 'Bojong Gede', activity: 'LOGIN_ADMIN_DESA', operator: 'Rahmat Hidayat', time: '2 hours ago', status: 'SUCCESS' }, - { id: 5, type: 'DOCUMENT', village: 'Tapos', activity: 'VERIFIKASI_SURAT_KEMATIAN', operator: 'Agus Setiawan', time: '4 hours ago', status: 'SUCCESS' }, -] +interface LogEntry { + id: string + createdAt: string + action: string + desc: string + username: string + village: string +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()) function AppLogsPage() { const { appId } = useParams({ from: '/apps/$appId/logs' }) + const [page, setPage] = useState(1) + const [search, setSearch] = useState('') + const [searchQuery, setSearchQuery] = useState('') + const isDesaPlus = appId === 'desa-plus' + const isMobile = useMediaQuery('(max-width: 768px)') + + const apiUrl = isDesaPlus ? API_URLS.getLogsAllVillages(page, searchQuery) : null + const { data: response, error, isLoading } = useSWR(apiUrl, fetcher) + const logs: LogEntry[] = response?.data?.log || [] + + const handleSearchChange = (val: string) => { + setSearch(val) + if (val.length >= 3 || val.length === 0) { + setSearchQuery(val) + setPage(1) + } + } + + const handleClearSearch = () => { + setSearch('') + setSearchQuery('') + setPage(1) + } + + const getActionColor = (action: string) => { + const a = action.toUpperCase() + if (a === 'LOGIN') return 'blue' + if (a === 'LOGOUT') return 'gray' + if (a === 'CREATE') return 'teal' + if (a === 'UPDATE') return 'orange' + if (a === 'DELETE') return 'red' + return 'brand-blue' + } + + if (!isDesaPlus) { + return ( + + + + Activity Logs + This feature is currently customized for Desa+. Other apps coming soon. + + + ) + } return ( - - - - {isDesaPlus ? 'Desa+ Service Logs' : 'Application Activity Logs'} - Detailed audit trail of all actions performed within the application instances. - - - - - + + + + + + + + + + Activity Logs + + + {isLoading ? 'Loading logs...' : `Auditing ${response?.data?.total || 0} events across all villages`} + + + + - - } + placeholder="Search action or village..." + leftSection={} + size="md" + rightSection={ + search ? ( + + + + ) : null + } + value={search} + onChange={(e) => handleSearchChange(e.currentTarget.value)} radius="md" + style={{ maxWidth: 500 }} + ml={40} /> -