diff options
| -rw-r--r-- | frontend/history-view.tsx | 93 |
1 files changed, 86 insertions, 7 deletions
diff --git a/frontend/history-view.tsx b/frontend/history-view.tsx index 2faa1c0..12af1ea 100644 --- a/frontend/history-view.tsx +++ b/frontend/history-view.tsx | |||
| @@ -3,7 +3,8 @@ | |||
| 3 | import { useState, useEffect } from "react" | 3 | import { useState, useEffect } from "react" |
| 4 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | 4 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" |
| 5 | import { Badge } from "@/components/ui/badge" | 5 | import { Badge } from "@/components/ui/badge" |
| 6 | import { FileText, Folder, Trash2, Edit, Plus } from "lucide-react" | 6 | import { Button } from "@/components/ui/button" |
| 7 | import { FileText, Folder, Trash2, Edit, Plus, ChevronLeft, ChevronRight } from "lucide-react" | ||
| 7 | import { DriveLogEntry } from "@/lib/drive_types" | 8 | import { DriveLogEntry } from "@/lib/drive_types" |
| 8 | import { formatFileSize } from "@/lib/utils" | 9 | import { formatFileSize } from "@/lib/utils" |
| 9 | 10 | ||
| @@ -62,10 +63,13 @@ function getActionBadge(action: string) { | |||
| 62 | } | 63 | } |
| 63 | } | 64 | } |
| 64 | 65 | ||
| 66 | const ENTRIES_PER_PAGE = 100 | ||
| 67 | |||
| 65 | export default function HistoryView() { | 68 | export default function HistoryView() { |
| 66 | const [logEntries, setLogEntries] = useState<DriveLogEntry[]>([]) | 69 | const [logEntries, setLogEntries] = useState<DriveLogEntry[]>([]) |
| 67 | const [loading, setLoading] = useState(true) | 70 | const [loading, setLoading] = useState(true) |
| 68 | const [error, setError] = useState<string | null>(null) | 71 | const [error, setError] = useState<string | null>(null) |
| 72 | const [currentPage, setCurrentPage] = useState(1) | ||
| 69 | 73 | ||
| 70 | useEffect(() => { | 74 | useEffect(() => { |
| 71 | async function fetchLogEntries() { | 75 | async function fetchLogEntries() { |
| @@ -88,6 +92,24 @@ export default function HistoryView() { | |||
| 88 | fetchLogEntries() | 92 | fetchLogEntries() |
| 89 | }, []) | 93 | }, []) |
| 90 | 94 | ||
| 95 | // Calculate pagination values | ||
| 96 | const totalPages = Math.ceil(logEntries.length / ENTRIES_PER_PAGE) | ||
| 97 | const startIndex = (currentPage - 1) * ENTRIES_PER_PAGE | ||
| 98 | const endIndex = startIndex + ENTRIES_PER_PAGE | ||
| 99 | const currentEntries = logEntries.slice(startIndex, endIndex) | ||
| 100 | |||
| 101 | const handlePageChange = (page: number) => { | ||
| 102 | setCurrentPage(page) | ||
| 103 | } | ||
| 104 | |||
| 105 | const handlePreviousPage = () => { | ||
| 106 | setCurrentPage(prev => Math.max(1, prev - 1)) | ||
| 107 | } | ||
| 108 | |||
| 109 | const handleNextPage = () => { | ||
| 110 | setCurrentPage(prev => Math.min(totalPages, prev + 1)) | ||
| 111 | } | ||
| 112 | |||
| 91 | if (loading) { | 113 | if (loading) { |
| 92 | return ( | 114 | return ( |
| 93 | <div className="space-y-6"> | 115 | <div className="space-y-6"> |
| @@ -136,27 +158,25 @@ export default function HistoryView() { | |||
| 136 | <Table> | 158 | <Table> |
| 137 | <TableHeader> | 159 | <TableHeader> |
| 138 | <TableRow> | 160 | <TableRow> |
| 139 | <TableHead className="w-[50px]">Action</TableHead> | ||
| 140 | <TableHead>Type</TableHead> | 161 | <TableHead>Type</TableHead> |
| 141 | <TableHead>File/Directory Name</TableHead> | 162 | <TableHead>File/Directory Name</TableHead> |
| 163 | <TableHead>Time</TableHead> | ||
| 142 | <TableHead>User</TableHead> | 164 | <TableHead>User</TableHead> |
| 143 | <TableHead>Timestamp</TableHead> | ||
| 144 | <TableHead>Size</TableHead> | 165 | <TableHead>Size</TableHead> |
| 145 | </TableRow> | 166 | </TableRow> |
| 146 | </TableHeader> | 167 | </TableHeader> |
| 147 | <TableBody> | 168 | <TableBody> |
| 148 | {logEntries.map((entry) => ( | 169 | {currentEntries.map((entry) => ( |
| 149 | <TableRow key={`${entry.log_id}`} className="hover:bg-muted/50"> | 170 | <TableRow key={`${entry.log_id}`} className="hover:bg-muted/50"> |
| 150 | <TableCell>{getActionIcon(entry.action)}</TableCell> | ||
| 151 | <TableCell>{getActionBadge(entry.action)}</TableCell> | 171 | <TableCell>{getActionBadge(entry.action)}</TableCell> |
| 152 | <TableCell className="font-medium"> | 172 | <TableCell className="font-medium"> |
| 153 | <span className="break-words">{entry.path}</span> | 173 | <span className="break-words">{entry.path}</span> |
| 154 | </TableCell> | 174 | </TableCell> |
| 155 | <TableCell> | 175 | <TableCell> |
| 156 | <span className="text-sm font-mono">{entry.email}</span> | 176 | <span className="text-sm">{formatTimestamp(entry.timestamp)}</span> |
| 157 | </TableCell> | 177 | </TableCell> |
| 158 | <TableCell> | 178 | <TableCell> |
| 159 | <span className="text-sm">{formatTimestamp(entry.timestamp)}</span> | 179 | <span className="text-sm font-mono">{entry.email}</span> |
| 160 | </TableCell> | 180 | </TableCell> |
| 161 | <TableCell> | 181 | <TableCell> |
| 162 | <span className="text-sm text-muted-foreground"> | 182 | <span className="text-sm text-muted-foreground"> |
| @@ -168,6 +188,65 @@ export default function HistoryView() { | |||
| 168 | </TableBody> | 188 | </TableBody> |
| 169 | </Table> | 189 | </Table> |
| 170 | </div> | 190 | </div> |
| 191 | |||
| 192 | {/* Pagination */} | ||
| 193 | {totalPages > 1 && ( | ||
| 194 | <div className="flex items-center justify-between"> | ||
| 195 | <div className="text-sm text-muted-foreground"> | ||
| 196 | Showing {startIndex + 1} to {Math.min(endIndex, logEntries.length)} of {logEntries.length} entries | ||
| 197 | </div> | ||
| 198 | <div className="flex items-center gap-2"> | ||
| 199 | <Button | ||
| 200 | variant="outline" | ||
| 201 | size="sm" | ||
| 202 | onClick={handlePreviousPage} | ||
| 203 | disabled={currentPage === 1} | ||
| 204 | > | ||
| 205 | <ChevronLeft className="h-4 w-4" /> | ||
| 206 | Previous | ||
| 207 | </Button> | ||
| 208 | |||
| 209 | <div className="flex items-center gap-1"> | ||
| 210 | {Array.from({ length: totalPages }, (_, i) => i + 1) | ||
| 211 | .filter(page => { | ||
| 212 | // Show first page, last page, current page, and pages around current | ||
| 213 | return page === 1 || | ||
| 214 | page === totalPages || | ||
| 215 | Math.abs(page - currentPage) <= 2 | ||
| 216 | }) | ||
| 217 | .map((page, index, filteredPages) => { | ||
| 218 | // Add ellipsis where there are gaps | ||
| 219 | const showEllipsisBefore = index > 0 && page - filteredPages[index - 1] > 1 | ||
| 220 | return ( | ||
| 221 | <div key={page} className="flex items-center gap-1"> | ||
| 222 | {showEllipsisBefore && ( | ||
| 223 | <span className="text-muted-foreground px-2">...</span> | ||
| 224 | )} | ||
| 225 | <Button | ||
| 226 | variant={currentPage === page ? "default" : "outline"} | ||
| 227 | size="sm" | ||
| 228 | onClick={() => handlePageChange(page)} | ||
| 229 | className="w-8 h-8 p-0" | ||
| 230 | > | ||
| 231 | {page} | ||
| 232 | </Button> | ||
| 233 | </div> | ||
| 234 | ) | ||
| 235 | })} | ||
| 236 | </div> | ||
| 237 | |||
| 238 | <Button | ||
| 239 | variant="outline" | ||
| 240 | size="sm" | ||
| 241 | onClick={handleNextPage} | ||
| 242 | disabled={currentPage === totalPages} | ||
| 243 | > | ||
| 244 | Next | ||
| 245 | <ChevronRight className="h-4 w-4" /> | ||
| 246 | </Button> | ||
| 247 | </div> | ||
| 248 | </div> | ||
| 249 | )} | ||
| 171 | </div> | 250 | </div> |
| 172 | ) | 251 | ) |
| 173 | } | 252 | } |
