import { useEffect, useState, useCallback, useMemo } from "react"; import { Authentication } from "../../../scripts/pocketbase/Authentication"; import { DataSyncService } from "../../../scripts/database/DataSyncService"; import { Collections } from "../../../schemas/pocketbase/schema"; import debounce from 'lodash/debounce'; import type { Log } from "../../../schemas/pocketbase"; import { SendLog } from "../../../scripts/pocketbase/SendLog"; const LOGS_PER_PAGE = 5; export default function ShowProfileLogs() { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalLogs, setTotalLogs] = useState(0); const [searchQuery, setSearchQuery] = useState(''); const [allLogs, setAllLogs] = useState([]); const [isFetchingAll, setIsFetchingAll] = useState(false); const [autoRefresh, setAutoRefresh] = useState(true); // Auto-refresh logs every 30 seconds if enabled useEffect(() => { if (!autoRefresh) return; const interval = setInterval(() => { if (!isFetchingAll) { fetchLogs(true); } }, 30000); // 30 seconds return () => clearInterval(interval); }, [autoRefresh, isFetchingAll]); const fetchLogs = async (skipCache = false) => { setLoading(true); setError(null); const auth = Authentication.getInstance(); const currentUser = auth.getPocketBase().authStore.model; const userId = currentUser?.id; if (!userId) { setError("Not authenticated"); setLoading(false); return; } try { setIsFetchingAll(true); // console.log("Fetching logs for user:", userId); // Use DataSyncService to fetch logs const dataSync = DataSyncService.getInstance(); // First sync logs for this user await dataSync.syncCollection( Collections.LOGS, `user = "${userId}"`, "-created", { expand: "user" } ); // Then get all logs from IndexedDB const fetchedLogs = await dataSync.getData( Collections.LOGS, false, // Don't force sync again `user = "${userId}"`, "-created" ); // console.log("Fetched logs:", fetchedLogs.length); if (fetchedLogs.length === 0) { // If no logs found, try to fetch directly from PocketBase // console.log("No logs found in IndexedDB, trying direct fetch from PocketBase"); try { const sendLog = SendLog.getInstance(); const directLogs = await sendLog.getUserLogs(userId); // console.log("Direct fetch logs:", directLogs.length); if (directLogs.length > 0) { setAllLogs(directLogs); setTotalPages(Math.ceil(directLogs.length / LOGS_PER_PAGE)); setTotalLogs(directLogs.length); } else { setAllLogs(fetchedLogs); setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE)); setTotalLogs(fetchedLogs.length); } } catch (directError) { // console.error("Failed to fetch logs directly:", directError); setAllLogs(fetchedLogs); setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE)); setTotalLogs(fetchedLogs.length); } } else { setAllLogs(fetchedLogs); setTotalPages(Math.ceil(fetchedLogs.length / LOGS_PER_PAGE)); setTotalLogs(fetchedLogs.length); } } catch (error) { // console.error("Failed to fetch logs:", error); setError("Error loading activity"); } finally { setLoading(false); setIsFetchingAll(false); } }; // Memoized search function const filteredLogs = useMemo(() => { if (!allLogs.length) return []; if (!searchQuery.trim()) { // When not searching, return only the current page of logs const startIndex = (currentPage - 1) * LOGS_PER_PAGE; const endIndex = startIndex + LOGS_PER_PAGE; return allLogs.slice(startIndex, endIndex); } const query = searchQuery.toLowerCase(); return allLogs.filter(log => { return ( log.message?.toLowerCase().includes(query) || log.type?.toLowerCase().includes(query) || log.part?.toLowerCase().includes(query) || (log.created && new Date(log.created).toLocaleString().toLowerCase().includes(query)) ); }); }, [searchQuery, allLogs, currentPage]); // Update displayed logs whenever filtered results change useEffect(() => { setLogs(filteredLogs); // console.log("Filtered logs updated:", filteredLogs.length, "logs"); }, [filteredLogs]); // Debounced search handler const debouncedSearch = useCallback( debounce((query: string) => { setSearchQuery(query); // Reset to first page when searching if (query.trim()) { setCurrentPage(1); } }, 300), [] ); const handleSearch = (event: React.ChangeEvent) => { debouncedSearch(event.target.value); }; const handlePrevPage = () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); } }; const handleNextPage = () => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }; const handleRefresh = () => { fetchLogs(true); }; useEffect(() => { const loadLogsWithRetry = async () => { try { await fetchLogs(); // Wait a moment for state to update setTimeout(async () => { // Check if logs were loaded if (allLogs.length === 0) { // console.log("No logs found after initial fetch, trying direct fetch"); await directFetchLogs(); } }, 1000); } catch (error) { // console.error("Failed to load logs with retry:", error); } }; loadLogsWithRetry(); checkLogsExist(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Check if there are any logs in the database at all const checkLogsExist = async () => { try { const auth = Authentication.getInstance(); const pb = auth.getPocketBase(); // Check if the logs collection exists and has any records const result = await pb.collection(Collections.LOGS).getList(1, 1); // console.log("Logs collection check:", { // totalItems: result.totalItems, // page: result.page, // perPage: result.perPage, // totalPages: result.totalPages // }); } catch (error) { // console.error("Failed to check logs collection:", error); } }; // Calculate log statistics const logStats = useMemo(() => { if (!allLogs.length) return null; const today = new Date(); today.setHours(0, 0, 0, 0); const lastWeek = new Date(today); lastWeek.setDate(lastWeek.getDate() - 7); const todayLogs = allLogs.filter(log => new Date(log.created) >= today); const weekLogs = allLogs.filter(log => new Date(log.created) >= lastWeek); // Count by type const typeCount: Record = {}; allLogs.forEach(log => { const type = log.type || 'unknown'; typeCount[type] = (typeCount[type] || 0) + 1; }); return { total: allLogs.length, today: todayLogs.length, week: weekLogs.length, types: typeCount }; }, [allLogs]); // Direct fetch from PocketBase as a fallback const directFetchLogs = async () => { try { setLoading(true); setError(null); const auth = Authentication.getInstance(); const pb = auth.getPocketBase(); const userId = auth.getPocketBase().authStore.model?.id; if (!userId) { setError("Not authenticated"); setLoading(false); return; } // console.log("Direct fetching logs for user:", userId); // Fetch logs directly from PocketBase const result = await pb.collection(Collections.LOGS).getList(1, 100, { filter: `user = "${userId}"`, sort: "-created", expand: "user" }); // console.log("Direct fetch result:", { // totalItems: result.totalItems, // items: result.items.length // }); if (result.items.length > 0) { setAllLogs(result.items); setTotalPages(Math.ceil(result.items.length / LOGS_PER_PAGE)); setTotalLogs(result.items.length); } } catch (error) { // console.error("Failed to direct fetch logs:", error); setError("Error loading activity"); } finally { setLoading(false); } }; // Add a button to try direct fetch const renderDirectFetchButton = () => ( ); if (loading && !allLogs.length) { return (

{isFetchingAll ? 'Fetching your activity...' : 'Loading activity...'}

); } if (error) { return (

{error}

); } // Debug logs // console.log("Render state:", { // logsLength: logs.length, // allLogsLength: allLogs.length, // searchQuery, // loading, // currentPage // }); if (allLogs.length === 0 && !searchQuery && !loading) { return (

No recent activity to display.

); } return (
{/* Activity Summary */} {logStats && !searchQuery && (
Today
{logStats.today}
Activities recorded today
This Week
{logStats.week}
Activities in the last 7 days
Total
{logStats.total}
All-time activities
)} {/* Search and Refresh Controls */}
{isFetchingAll && (

Fetching all activity, please wait...

)} {/* Search Results Message */} {searchQuery && (

Found {logs.length} results for "{searchQuery}"

)} {/* Logs Display */}
{logs.map((log) => (
{getLogTypeIcon(log.type)}

{log.message}

{log.part && ( {log.part} )}

{new Date(log.created).toLocaleString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}

))}
{/* Pagination Controls */} {!searchQuery && totalLogs > LOGS_PER_PAGE && (
Showing {totalLogs ? (currentPage - 1) * LOGS_PER_PAGE + 1 : 0}-{Math.min(currentPage * LOGS_PER_PAGE, totalLogs)} of {totalLogs} results
)}
); } function getLogTypeColor(type: string): string { switch (type?.toLowerCase()) { case 'error': return 'bg-error/10 text-error'; case 'update': return 'bg-info/10 text-info'; case 'delete': return 'bg-warning/10 text-warning'; case 'create': return 'bg-success/10 text-success'; case 'login': case 'logout': return 'bg-primary/10 text-primary'; default: return 'bg-base-300/50 text-base-content'; } } function getLogTypeIcon(type: string) { switch (type?.toLowerCase()) { case 'error': return ( ); case 'update': return ( ); case 'delete': return ( ); case 'create': return ( ); case 'login': return ( ); case 'logout': return ( ); default: return ( ); } }