summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-08-14 15:07:28 +0100
committerdiogo464 <[email protected]>2025-08-14 15:07:28 +0100
commit912bef7608aab286a5cc82c8ac9e2e19b19b5f1c (patch)
tree8896efe5081b7cf11606ca5a6270f388fa324be3 /frontend
parente49771f9c97110b4e0d66c796716c43dd92166c4 (diff)
feat: add paginated history page with activity log
- Create /history page showing drive activity with server-side rendering - Display timestamp, action, user, path, and file size in table format - Add pagination (50 entries per page) using URL query parameters - Sort entries by timestamp descending (most recent first) - Add History button to drive header for easy navigation - Use existing UI components and styling patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Diffstat (limited to 'frontend')
-rw-r--r--frontend/app/history/page.tsx35
-rw-r--r--frontend/components/drive/DriveHeader.tsx14
-rw-r--r--frontend/components/history/HistoryView.tsx138
3 files changed, 185 insertions, 2 deletions
diff --git a/frontend/app/history/page.tsx b/frontend/app/history/page.tsx
new file mode 100644
index 0000000..9ccba27
--- /dev/null
+++ b/frontend/app/history/page.tsx
@@ -0,0 +1,35 @@
1import { Drive_log } from "@/lib/drive_server"
2import { HistoryView } from "@/components/history/HistoryView"
3
4interface HistoryPageProps {
5 searchParams: { [key: string]: string | string[] | undefined }
6}
7
8export default async function HistoryPage({ searchParams }: HistoryPageProps) {
9 const logEntries = await Drive_log()
10
11 // Parse page parameter (default to 0)
12 const page = parseInt((searchParams.page as string) || '0', 10)
13 const pageSize = 50
14
15 // Sort by timestamp descending (most recent first)
16 const sortedEntries = logEntries.sort((a, b) => b.timestamp - a.timestamp)
17
18 // Calculate pagination
19 const startIndex = page * pageSize
20 const endIndex = startIndex + pageSize
21 const paginatedEntries = sortedEntries.slice(startIndex, endIndex)
22
23 const hasNextPage = endIndex < sortedEntries.length
24 const hasPrevPage = page > 0
25
26 return (
27 <HistoryView
28 entries={paginatedEntries}
29 currentPage={page}
30 hasNextPage={hasNextPage}
31 hasPrevPage={hasPrevPage}
32 totalEntries={sortedEntries.length}
33 />
34 )
35} \ No newline at end of file
diff --git a/frontend/components/drive/DriveHeader.tsx b/frontend/components/drive/DriveHeader.tsx
index 718d031..76b0e40 100644
--- a/frontend/components/drive/DriveHeader.tsx
+++ b/frontend/components/drive/DriveHeader.tsx
@@ -1,5 +1,7 @@
1import { HardDrive } from "lucide-react" 1import { HardDrive, Clock } from "lucide-react"
2import { AuthButtons } from "@/components/auth/AuthButtons" 2import { AuthButtons } from "@/components/auth/AuthButtons"
3import Link from "next/link"
4import { Button } from "@/components/ui/button"
3 5
4export async function DriveHeader() { 6export async function DriveHeader() {
5 return ( 7 return (
@@ -9,7 +11,15 @@ export async function DriveHeader() {
9 <h1 className="text-2xl font-bold">FCT Drive</h1> 11 <h1 className="text-2xl font-bold">FCT Drive</h1>
10 </div> 12 </div>
11 13
12 <AuthButtons /> 14 <div className="flex items-center gap-2">
15 <Link href="/history">
16 <Button variant="outline" size="sm">
17 <Clock className="mr-2 h-4 w-4" />
18 History
19 </Button>
20 </Link>
21 <AuthButtons />
22 </div>
13 </div> 23 </div>
14 ) 24 )
15} \ No newline at end of file 25} \ No newline at end of file
diff --git a/frontend/components/history/HistoryView.tsx b/frontend/components/history/HistoryView.tsx
new file mode 100644
index 0000000..1fe3cd2
--- /dev/null
+++ b/frontend/components/history/HistoryView.tsx
@@ -0,0 +1,138 @@
1import { DriveLogEntry } from "@/lib/drive_types"
2import { DriveHeader } from "@/components/drive/DriveHeader"
3import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
4import { Button } from "@/components/ui/button"
5import { ChevronLeft, ChevronRight } from "lucide-react"
6import Link from "next/link"
7
8interface HistoryViewProps {
9 entries: DriveLogEntry[]
10 currentPage: number
11 hasNextPage: boolean
12 hasPrevPage: boolean
13 totalEntries: number
14}
15
16function formatFileSize(bytes: number): string {
17 if (bytes === 0) return "0 Bytes"
18 const k = 1024
19 const sizes = ["Bytes", "KB", "MB", "GB"]
20 const i = Math.floor(Math.log(bytes) / Math.log(k))
21 return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
22}
23
24function formatDateTime(timestamp: number): string {
25 return new Date(timestamp * 1000).toLocaleString()
26}
27
28function PaginationControls({ currentPage, hasNextPage, hasPrevPage }: {
29 currentPage: number
30 hasNextPage: boolean
31 hasPrevPage: boolean
32}) {
33 return (
34 <div className="flex items-center justify-center gap-2">
35 <Link href={`/history?page=${currentPage - 1}`}>
36 <Button
37 variant="outline"
38 size="sm"
39 disabled={!hasPrevPage}
40 className={!hasPrevPage ? "opacity-50 cursor-not-allowed" : ""}
41 >
42 <ChevronLeft className="h-4 w-4 mr-1" />
43 Previous
44 </Button>
45 </Link>
46
47 <span className="text-sm text-muted-foreground px-4">
48 Page {currentPage + 1}
49 </span>
50
51 <Link href={`/history?page=${currentPage + 1}`}>
52 <Button
53 variant="outline"
54 size="sm"
55 disabled={!hasNextPage}
56 className={!hasNextPage ? "opacity-50 cursor-not-allowed" : ""}
57 >
58 Next
59 <ChevronRight className="h-4 w-4 ml-1" />
60 </Button>
61 </Link>
62 </div>
63 )
64}
65
66export function HistoryView({ entries, currentPage, hasNextPage, hasPrevPage, totalEntries }: HistoryViewProps) {
67 return (
68 <div className="container mx-auto p-6 space-y-6">
69 <DriveHeader />
70
71 <div className="space-y-4">
72 <div className="flex items-center justify-between">
73 <h1 className="text-2xl font-bold">Activity History</h1>
74 <div className="text-sm text-muted-foreground">
75 Showing {entries.length} of {totalEntries} entries
76 </div>
77 </div>
78
79 <PaginationControls
80 currentPage={currentPage}
81 hasNextPage={hasNextPage}
82 hasPrevPage={hasPrevPage}
83 />
84
85 <div className="border rounded-lg">
86 <Table>
87 <TableHeader>
88 <TableRow>
89 <TableHead>Timestamp</TableHead>
90 <TableHead>Action</TableHead>
91 <TableHead>User</TableHead>
92 <TableHead>Path</TableHead>
93 <TableHead>Size</TableHead>
94 </TableRow>
95 </TableHeader>
96 <TableBody>
97 {entries.length === 0 ? (
98 <TableRow>
99 <TableCell colSpan={5} className="text-center py-8 text-muted-foreground">
100 No activity history found
101 </TableCell>
102 </TableRow>
103 ) : (
104 entries.map((entry) => (
105 <TableRow key={entry.log_id} className="hover:bg-muted/50">
106 <TableCell className="font-mono text-sm">
107 {formatDateTime(entry.timestamp)}
108 </TableCell>
109 <TableCell>
110 <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
111 {entry.action}
112 </span>
113 </TableCell>
114 <TableCell className="font-medium">
115 {entry.email}
116 </TableCell>
117 <TableCell className="font-mono text-sm max-w-md truncate">
118 {entry.path}
119 </TableCell>
120 <TableCell>
121 {formatFileSize(entry.size)}
122 </TableCell>
123 </TableRow>
124 ))
125 )}
126 </TableBody>
127 </Table>
128 </div>
129
130 <PaginationControls
131 currentPage={currentPage}
132 hasNextPage={hasNextPage}
133 hasPrevPage={hasPrevPage}
134 />
135 </div>
136 </div>
137 )
138} \ No newline at end of file