From 0afc2e271a038af52a4d3a26d56ba3d2a49f95eb Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 22 May 2026 11:37:37 +0800 Subject: [PATCH] feat: improve logs page with debounce, action/village/date filters, and timestamp fix --- src/frontend/config/api.ts | 10 +- src/frontend/routes/apps.$appId.logs.tsx | 153 +++++++++++++++++------ 2 files changed, 122 insertions(+), 41 deletions(-) diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts index 6fa811d..434ae57 100644 --- a/src/frontend/config/api.ts +++ b/src/frontend/config/api.ts @@ -18,8 +18,14 @@ export const API_URLS = { 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)}`, + getLogsAllVillages: (page: number, search: string, action?: string, idVillage?: string, dateFrom?: string, dateTo?: string) => { + const params = new URLSearchParams({ page: String(page), search }) + if (action) params.set('action', action) + if (idVillage) params.set('idVillage', idVillage) + if (dateFrom) params.set('dateFrom', dateFrom) + if (dateTo) params.set('dateTo', dateTo) + return `${DESA_PLUS_PROXY}/api/monitoring/log-all-villages?${params}` + }, getGridOverview: () => `${DESA_PLUS_PROXY}/api/monitoring/grid-overview`, getDailyActivity: () => `${DESA_PLUS_PROXY}/api/monitoring/daily-activity`, getComparisonActivity: () => `${DESA_PLUS_PROXY}/api/monitoring/comparison-activity`, diff --git a/src/frontend/routes/apps.$appId.logs.tsx b/src/frontend/routes/apps.$appId.logs.tsx index 157aa3e..7e6a671 100644 --- a/src/frontend/routes/apps.$appId.logs.tsx +++ b/src/frontend/routes/apps.$appId.logs.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import useSWR from 'swr' import { ActionIcon, @@ -10,6 +10,7 @@ import { Pagination, Paper, ScrollArea, + Select, Stack, Table, Text, @@ -17,10 +18,12 @@ import { Title, Tooltip, } from '@mantine/core' -import { useMediaQuery } from '@mantine/hooks' +import { useDebouncedValue, useMediaQuery } from '@mantine/hooks' +import { DatePickerInput } from '@mantine/dates' import { createFileRoute, useParams } from '@tanstack/react-router' import { TbAlertCircle, + TbCalendar, TbHistory, TbHome2, TbSearch, @@ -51,30 +54,75 @@ const ACTION_COLOR: Record = { 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 apiUrl = isDesaPlus ? API_URLS.getLogsAllVillages(page, searchQuery) : null + 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 handleSearchChange = (val: string) => { - setSearch(val) - if (val.length >= 3 || val.length === 0) { - setSearchQuery(val) + 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('') @@ -108,23 +156,61 @@ function AppLogsPage() { - } - size="sm" - rightSection={ - search ? ( - - - - - - ) : null - } - value={search} - onChange={(e) => handleSearchChange(e.currentTarget.value)} - radius="md" - /> + + } + 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 ? ( @@ -143,7 +229,7 @@ function AppLogsPage() { - {searchQuery ? 'No activity found for this search.' : 'No activity logs yet.'} + {searchQuery || filterAction || filterVillageId || dateFrom ? 'No activity found for this filter.' : 'No activity logs yet.'} @@ -174,18 +260,7 @@ function AppLogsPage() { {logs.map((log) => ( - {log.createdAt.endsWith('lalu') ? ( - {log.createdAt} - ) : ( - - - {log.createdAt.split(' ').slice(1).join(' ')} - - - {log.createdAt.split(' ')[0]} - - - )} + @@ -229,7 +304,7 @@ function AppLogsPage() { )} - {!isLoading && !error && response?.data?.totalPage > 0 && ( + {!isLoading && !error && response?.data?.totalPage > 1 && (