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}
-
+ */}
)
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.