import { useEffect, useState } from 'react' import useSWR from 'swr' import { ActionIcon, Avatar, Badge, Code, Group, Loader, Pagination, Paper, ScrollArea, Select, Stack, Table, Text, TextInput, Title, Tooltip, } from '@mantine/core' import { useDebouncedValue, useMediaQuery } from '@mantine/hooks' import { DatePickerInput } from '@mantine/dates' import { createFileRoute, useParams } from '@tanstack/react-router' import { TbAlertCircle, TbCalendar, TbHistory, TbHome2, TbSearch, TbX, } from 'react-icons/tb' import { API_URLS } from '../config/api' export const Route = createFileRoute('/apps/$appId/logs')({ component: AppLogsPage, }) interface LogEntry { id: string createdAt: string action: string desc: string username: string village: string } const fetcher = (url: string) => fetch(url).then((res) => res.json()) const ACTION_COLOR: Record = { LOGIN: 'teal', LOGOUT: 'gray', CREATE: 'blue', UPDATE: 'yellow', DELETE: 'red', } const ACTION_OPTIONS = [ { value: 'LOGIN', label: 'Login' }, { value: 'LOGOUT', label: 'Logout' }, { value: 'CREATE', label: 'Create' }, { value: 'UPDATE', label: 'Update' }, { value: 'DELETE', label: 'Delete' }, ] function getActionColor(action: string) { return ACTION_COLOR[action.toUpperCase()] ?? 'brand-blue' } function LogTimestamp({ value }: { value: string }) { if (value.endsWith('lalu')) { return {value} } const [time, ...dateParts] = value.split(' ') return ( {dateParts.join(' ')} {time} ) } function AppLogsPage() { const { appId } = useParams({ from: '/apps/$appId/logs' }) const [page, setPage] = useState(1) const [search, setSearch] = useState('') const [searchQuery, setSearchQuery] = useState('') const [debouncedSearch] = useDebouncedValue(search, 400) const [filterAction, setFilterAction] = useState(null) const [filterVillageSearch, setFilterVillageSearch] = useState('') const [filterVillageId, setFilterVillageId] = useState(null) const [dateRange, setDateRange] = useState<[string | null, string | null]>([null, null]) const isDesaPlus = appId === 'desa-plus' const isMobile = useMediaQuery('(max-width: 768px)') const [dateFrom, dateTo] = dateRange const apiUrl = isDesaPlus ? API_URLS.getLogsAllVillages( page, searchQuery, filterAction ?? undefined, filterVillageId ?? undefined, dateFrom ?? undefined, dateTo ?? undefined, ) : null const { data: response, error, isLoading } = useSWR(apiUrl, fetcher) const logs: LogEntry[] = response?.data?.log || [] const { data: filterVillagesResp } = useSWR( isDesaPlus && filterVillageSearch.length >= 1 ? API_URLS.getVillages(1, filterVillageSearch) : null, fetcher ) const filterVillagesOptions = (filterVillagesResp?.data || []).map((v: any) => ({ value: v.id, label: v.name })) useEffect(() => { if (debouncedSearch.length >= 3 || debouncedSearch.length === 0) { setSearchQuery(debouncedSearch) setPage(1) } }, [debouncedSearch]) useEffect(() => { setPage(1) }, [filterAction, filterVillageId, dateFrom, dateTo]) const handleClearSearch = () => { setSearch('') setSearchQuery('') setPage(1) } if (!isDesaPlus) { return ( Activity Logs — Coming Soon This feature is currently available for Desa+. Other apps coming soon. ) } return ( Activity Logs {isLoading ? 'Loading logs...' : `${(response?.data?.total ?? 0).toLocaleString()} events across all villages`} } size="sm" rightSection={ search ? ( ) : null } value={search} onChange={(e) => setSearch(e.currentTarget.value)} radius="md" /> } value={dateRange} onChange={setDateRange} radius="md" clearable style={{ flex: 1 }} maxDate={new Date()} /> {isLoading ? ( ) : error ? ( Failed to load logs from the API. ) : logs.length === 0 ? ( {searchQuery || filterAction || filterVillageId || dateFrom ? 'No activity found for this filter.' : 'No activity logs yet.'} ) : ( Timestamp User & Village Action Description {logs.map((log) => ( {log.username.charAt(0)} {log.username} {log.village} {log.action} {log.desc} ))}
)} {!isLoading && !error && response?.data?.totalPage > 1 && ( )}
) }