diff --git a/src/frontend/components/AppCard.tsx b/src/frontend/components/AppCard.tsx index 130c03b..3a8d485 100644 --- a/src/frontend/components/AppCard.tsx +++ b/src/frontend/components/AppCard.tsx @@ -46,7 +46,7 @@ export function AppCard({ id, name, status, users, errors, version }: AppCardPro {name} - BUILD v{version} + VERSION {version} @@ -54,7 +54,7 @@ export function AppCard({ id, name, status, users, errors, version }: AppCardPro - + {/* @@ -70,13 +70,13 @@ export function AppCard({ id, name, status, users, errors, version }: AppCardPro 0 ? '#ef4444' : '#64748b'} /> - HEALTH INCIDENTS + ERROR 0 ? 'red' : 'dimmed'}>{errors} 0 ? 30 : 0} size="sm" color="red" radius="xl" /> - + */} ) diff --git a/src/frontend/components/DashboardLayout.tsx b/src/frontend/components/DashboardLayout.tsx index 133c8ad..1f59101 100644 --- a/src/frontend/components/DashboardLayout.tsx +++ b/src/frontend/components/DashboardLayout.tsx @@ -27,7 +27,9 @@ import { TbSettings, TbUserCircle, TbSun, - TbMoon + TbMoon, + TbUser, + TbHistory } from 'react-icons/tb' interface DashboardLayoutProps { @@ -49,7 +51,8 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { const globalNav = [ { label: 'Dashboard', icon: TbDashboard, to: '/dashboard' }, { label: 'Applications', icon: TbApps, to: '/apps' }, - { label: 'Settings', icon: TbSettings, to: '/settings' }, + { label: 'Log Activity', icon: TbHistory, to: '/logs' }, + { label: 'Users', icon: TbUser, to: '/users' }, ] const activeApp = appId ? APP_CONFIGS[appId] : null @@ -166,7 +169,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { mb={"md"} variant="filled" styles={(theme) => ({ - input: { border: '1px solid rgba(255,255,255,0.1)' } + input: { border: computedColorScheme === 'dark' ? '1px solid rgba(255,255,255,0.1)' : '1px solid rgba(0,0,0,0.1)' } })} /> } @@ -215,7 +218,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { SYSTEM STATUS diff --git a/src/frontend/routes/logs.tsx b/src/frontend/routes/logs.tsx new file mode 100644 index 0000000..4fbc5e6 --- /dev/null +++ b/src/frontend/routes/logs.tsx @@ -0,0 +1,214 @@ +import { + Badge, + Container, + Group, + Stack, + Text, + Paper, + TextInput, + Select, + Avatar, + Box, + Divider, +} from '@mantine/core' +import { useState, useMemo } from 'react' +import { createFileRoute } from '@tanstack/react-router' +import { TbSearch, TbClock, TbCheck, TbX } from 'react-icons/tb' +import { DashboardLayout } from '@/frontend/components/DashboardLayout' + +export const Route = createFileRoute('/logs')({ + component: GlobalLogsPage, +}) + +const timelineData = [ + { + date: 'TODAY', + logs: [ + { id: 1, time: '12:12 PM', operator: 'Budi Santoso', app: 'Desa+', color: 'blue', content: <>generated document Surat Domisili for Sukatani }, + { id: 2, time: '11:42 AM', operator: 'Siti Aminah', app: 'Desa+', color: 'teal', content: <>uploaded financial report Realisasi Q1 for Sukamaju }, + { id: 3, time: '10:12 AM', operator: 'System', app: 'Desa+', color: 'red', icon: TbX, content: <>experienced failure in SIAK Sync at }>Cikini, message: { title: 'Sync Operation Failed (NullPointerException)', text: 'NullPointerException at village_sync.dart:45. The server returned a timeout error while waiting for the master database replica connection. Auto-retry scheduled in 15 minutes.' } }, + { id: 4, time: '09:42 AM', operator: 'Jane Smith', app: 'E-Commerce', color: 'orange', icon: TbCheck, content: <>resolved payment gateway issue for E-Commerce checkout }, + ] + }, + { + date: 'YESTERDAY', + logs: [ + { id: 5, time: '05:10 AM', operator: 'System', app: 'System', color: 'cyan', content: <>completed automated Nightly Backup for all 138 villages }, + { id: 6, time: '04:50 AM', operator: 'Rahmat Hidayat', app: 'Desa+', color: 'green', content: <>granted Admin access to Desa Bojong Gede operator }, + { id: 7, time: '03:42 AM', operator: 'System', app: 'Fitness App', color: 'red', icon: TbX, content: <>detected SocketException across Fitness App wearable sync operations. }, + { id: 8, time: '02:33 AM', operator: 'Agus Setiawan', app: 'Desa+', color: 'blue', content: <>verified 145 Surat Kematian entries in batch. }, + ] + }, + { + date: '12 APRIL, 2026', + logs: [ + { id: 9, time: '03:42 AM', operator: 'Amel', app: 'Desa+', color: 'indigo', content: <>changed version configurations rolling out Desa+ v2.4.1 }, + { id: 10, time: '02:10 AM', operator: 'John Doe', app: 'E-Commerce', color: 'pink', content: <>updated App setting Require OTP on Login View Details }, + ] + } +] + +function GlobalLogsPage() { + const [search, setSearch] = useState('') + const [appFilter, setAppFilter] = useState(null) + const [operatorFilter, setOperatorFilter] = useState(null) + + const filteredTimeline = useMemo(() => { + return timelineData + .map(group => { + const filteredLogs = group.logs.filter(log => { + if (appFilter && log.app !== appFilter) return false; + if (operatorFilter && log.operator !== operatorFilter) return false; + if (search) { + const lSearch = search.toLowerCase(); + if (!log.operator.toLowerCase().includes(lSearch) && !log.app.toLowerCase().includes(lSearch)) { + return false; + } + } + return true; + }); + return { ...group, logs: filteredLogs }; + }) + .filter(group => group.logs.length > 0); + }, [search, appFilter, operatorFilter]); + + return ( + + + + {/* Header Controls */} + + } + radius="md" + w={220} + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + + {/* Timeline Content */} + + {filteredTimeline.length === 0 ? ( + No logs found matching your filters. + ) : filteredTimeline.map((group, groupIndex) => ( + + 0 ? "xl" : 0} + mb="lg" + style={{ textTransform: 'uppercase' }} + > + {group.date} + + + + {group.logs.map((log, logIndex) => { + const isLastLog = logIndex === group.logs.length - 1; + + return ( + + {/* Left: Time */} + + {log.time} + + + {/* Middle: Line & Avatar */} + + {/* Vertical Line */} + {!isLastLog && ( + + )} + {/* Avatar */} + + {log.icon ? ( + + + + ) : ( + + {log.operator.charAt(0)} + + )} + + + + {/* Right: Content */} + + + {log.operator} + {log.content} + + + {log.message && ( + + {log.message.title} + + {log.message.text} + + + )} + + + ) + })} + + + {groupIndex < timelineData.length - 1 && ( + + )} + + ))} + + + + ) +} diff --git a/src/frontend/routes/settings.tsx b/src/frontend/routes/users.tsx similarity index 97% rename from src/frontend/routes/settings.tsx rename to src/frontend/routes/users.tsx index c4a3ada..fcbf60a 100644 --- a/src/frontend/routes/settings.tsx +++ b/src/frontend/routes/users.tsx @@ -35,8 +35,8 @@ import { import { DashboardLayout } from '@/frontend/components/DashboardLayout' import { StatsCard } from '@/frontend/components/StatsCard' -export const Route = createFileRoute('/settings')({ - component: SettingsPage, +export const Route = createFileRoute('/users')({ + component: UsersPage, }) const mockUsers = [ @@ -67,14 +67,14 @@ const roles = [ }, ] -function SettingsPage() { +function UsersPage() { return ( - Settings + Users Manage system users, security roles, and application access control.