diff --git a/frontends/nextjs/src/components/level5/tabs/ErrorLogsTab.tsx b/frontends/nextjs/src/components/level5/tabs/ErrorLogsTab.tsx index 3a7ac2874..ae549e392 100644 --- a/frontends/nextjs/src/components/level5/tabs/ErrorLogsTab.tsx +++ b/frontends/nextjs/src/components/level5/tabs/ErrorLogsTab.tsx @@ -1,801 +1,81 @@ "use client" -import { useState, useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' -import { Button } from '@/components/ui' -import { Badge } from '@/components/ui' -import { ScrollArea } from '@/components/ui' -import { Input } from '@/components/ui' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui' -import { Warning, CheckCircle, Info, Trash, Broom } from '@phosphor-icons/react' -import { Database } from '@/lib/database' -import type { ErrorLog } from '@/lib/db/error-logs' +import { useState } from 'react' +import { Card, CardContent, CardHeader } from '@/components/ui' import type { User } from '@/lib/level-types' -import { toast } from 'sonner' +import { clearErrorLogs, deleteErrorLog, markErrorResolved } from './error-logs/errorLogActions' +import { ErrorLogControls } from './error-logs/ErrorLogControls' +import { ErrorLogList } from './error-logs/ErrorLogList' +import { ErrorLogStats } from './error-logs/ErrorLogStats' +import { ClearLogsDialog } from './error-logs/ClearLogsDialog' +import { filterLogs, useErrorLogFilters } from './error-logs/useErrorLogFilters' +import { useErrorLogs } from './error-logs/useErrorLogs' interface ErrorLogsTabProps { user?: User // Optional: If provided, filters logs by user's tenantId (for God tier) } export function ErrorLogsTab({ user }: ErrorLogsTabProps) { - const [logs, setLogs] = useState([]) - const [loading, setLoading] = useState(false) - const [filterLevel, setFilterLevel] = useState('all') - const [filterResolved, setFilterResolved] = useState('all') + const { logs, loading, stats, reload, isSuperGod } = useErrorLogs(user) + const { filters, setFilterLevel, setFilterResolution } = useErrorLogFilters() const [showClearDialog, setShowClearDialog] = useState(false) const [clearOnlyResolved, setClearOnlyResolved] = useState(false) - const [stats, setStats] = useState({ - total: 0, - errors: 0, - warnings: 0, - info: 0, - resolved: 0, - unresolved: 0, - }) - // Determine access level based on user role - const isSuperGod = user?.role === 'supergod' - const tenantId = user?.tenantId - - useEffect(() => { - loadLogs() - }, []) - - const loadLogs = async () => { - setLoading(true) - try { - // SuperGod sees all logs, God sees only their tenant's logs - const options = isSuperGod ? {} : { tenantId } - const data = await Database.getErrorLogs(options) - setLogs(data) - calculateStats(data) - } catch (err) { - toast.error('Failed to load error logs') - console.error('Error loading logs:', err) - } finally { - setLoading(false) - } - } - - const calculateStats = (logs: ErrorLog[]) => { - setStats({ - total: logs.length, - errors: logs.filter(l => l.level === 'error').length, - warnings: logs.filter(l => l.level === 'warning').length, - info: logs.filter(l => l.level === 'info').length, - resolved: logs.filter(l => l.resolved).length, - unresolved: logs.filter(l => !l.resolved).length, - }) - } + const filteredLogs = filterLogs(logs, filters) const handleMarkResolved = async (id: string) => { - try { - await Database.updateErrorLog(id, { - resolved: true, - resolvedAt: Date.now(), - resolvedBy: user?.username || 'admin', - }) - await loadLogs() - toast.success('Error log marked as resolved') - } catch (err) { - toast.error('Failed to update error log') - } + await markErrorResolved(id, reload, user) } const handleDeleteLog = async (id: string) => { - try { - await Database.deleteErrorLog(id) - await loadLogs() - toast.success('Error log deleted') - } catch (err) { - toast.error('Failed to delete error log') - } + await deleteErrorLog(id, reload) } const handleClearLogs = async () => { - try { - const count = await Database.clearErrorLogs(clearOnlyResolved) - await loadLogs() - toast.success(`Cleared ${count} error log${count !== 1 ? 's' : ''}`) - setShowClearDialog(false) - } catch (err) { - toast.error('Failed to clear error logs') - } + await clearErrorLogs(clearOnlyResolved, reload, () => setShowClearDialog(false)) } - const getLevelIcon = (level: string) => { - switch (level) { - case 'error': - return - case 'warning': - return - case 'info': - return - default: - return - } + const openClearDialog = (onlyResolved: boolean) => { + setClearOnlyResolved(onlyResolved) + setShowClearDialog(true) } - const getLevelColor = (level: string) => { - switch (level) { - case 'error': - return 'bg-red-500/20 text-red-400 border-red-500/50' - case 'warning': - return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50' - case 'info': - return 'bg-blue-500/20 text-blue-400 border-blue-500/50' - default: - return 'bg-gray-500/20 text-gray-400 border-gray-500/50' - } - } - - const filteredLogs = logs.filter(log => { - if (filterLevel !== 'all' && log.level !== filterLevel) return false - if (filterResolved === 'resolved' && !log.resolved) return false - if (filterResolved === 'unresolved' && log.resolved) return false - return true - }) - - const scopeDescription = isSuperGod - ? 'All error logs across all tenants' - : `Error logs for your tenant only` - return (
-
- - - Total - - -
{stats.total}
-
-
- - - - Errors - - -
{stats.errors}
-
-
- - - - Warnings - - -
{stats.warnings}
-
-
- - - - Info - - -
{stats.info}
-
-
- - - - Resolved - - -
{stats.resolved}
-
-
- - - - Unresolved - - -
{stats.unresolved}
-
-
-
+ -
-
- System Error Logs - - {scopeDescription} - -
-
- - {isSuperGod && ( - <> - - - - )} -
-
- -
- - - -
+
- -
- {filteredLogs.length === 0 && !loading && ( -
- No error logs found -
- )} - - {filteredLogs.map((log) => ( - - -
-
-
-
- {getLevelIcon(log.level)} -
- - {log.level.toUpperCase()} - - {log.resolved && ( - - - Resolved - - )} - - {new Date(log.timestamp).toLocaleString()} - - {isSuperGod && log.tenantId && ( - - Tenant: {log.tenantId} - - )} -
- -
-

{log.message}

- {log.source && ( -

- Source: {log.source} -

- )} - {log.username && ( -

- User: {log.username} {log.userId && `(${log.userId})`} -

- )} -
- - {log.stack && ( -
- - Stack trace - -
-                              {log.stack}
-                            
-
- )} - - {log.context && ( -
- - Context - -
-                              {JSON.stringify(JSON.parse(log.context), null, 2)}
-                            
-
- )} - - {log.resolved && log.resolvedAt && ( -

- Resolved on {new Date(log.resolvedAt).toLocaleString()} - {log.resolvedBy && ` by ${log.resolvedBy}`} -

- )} -
- -
- {!log.resolved && ( - - )} - {isSuperGod && ( - - )} -
-
-
-
- ))} -
-
+
{isSuperGod && ( - - - - - - Confirm Clear Error Logs - - - {clearOnlyResolved - ? 'This will permanently delete all resolved error logs. This action cannot be undone.' - : 'This will permanently delete ALL error logs. This action cannot be undone.'} - - - - - Cancel - - - Clear Logs - - - - + )}
) -} - const [logs, setLogs] = useState([]) - const [loading, setLoading] = useState(false) - const [filterLevel, setFilterLevel] = useState('all') - const [filterResolved, setFilterResolved] = useState('all') - const [showClearDialog, setShowClearDialog] = useState(false) - const [clearOnlyResolved, setClearOnlyResolved] = useState(false) - const [stats, setStats] = useState({ - total: 0, - errors: 0, - warnings: 0, - info: 0, - resolved: 0, - unresolved: 0, - }) - - useEffect(() => { - loadLogs() - }, []) - - const loadLogs = async () => { - setLoading(true) - try { - const data = await Database.getErrorLogs() - setLogs(data) - calculateStats(data) - } catch (error) { - toast.error('Failed to load error logs') - console.error('Error loading logs:', error) - } finally { - setLoading(false) - } - } - - const calculateStats = (logs: ErrorLog[]) => { - setStats({ - total: logs.length, - errors: logs.filter(l => l.level === 'error').length, - warnings: logs.filter(l => l.level === 'warning').length, - info: logs.filter(l => l.level === 'info').length, - resolved: logs.filter(l => l.resolved).length, - unresolved: logs.filter(l => !l.resolved).length, - }) - } - - const handleMarkResolved = async (id: string) => { - try { - await Database.updateErrorLog(id, { - resolved: true, - resolvedAt: Date.now(), - resolvedBy: 'supergod', - }) - await loadLogs() - toast.success('Error log marked as resolved') - } catch (error) { - toast.error('Failed to update error log') - } - } - - const handleDeleteLog = async (id: string) => { - try { - await Database.deleteErrorLog(id) - await loadLogs() - toast.success('Error log deleted') - } catch (error) { - toast.error('Failed to delete error log') - } - } - - const handleClearLogs = async () => { - try { - const count = await Database.clearErrorLogs(clearOnlyResolved) - await loadLogs() - toast.success(`Cleared ${count} error log${count !== 1 ? 's' : ''}`) - setShowClearDialog(false) - } catch (error) { - toast.error('Failed to clear error logs') - } - } - - const getLevelIcon = (level: string) => { - switch (level) { - case 'error': - return - case 'warning': - return - case 'info': - return - default: - return - } - } - - const getLevelColor = (level: string) => { - switch (level) { - case 'error': - return 'bg-red-500/20 text-red-400 border-red-500/50' - case 'warning': - return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50' - case 'info': - return 'bg-blue-500/20 text-blue-400 border-blue-500/50' - default: - return 'bg-gray-500/20 text-gray-400 border-gray-500/50' - } - } - - const filteredLogs = logs.filter(log => { - if (filterLevel !== 'all' && log.level !== filterLevel) return false - if (filterResolved === 'resolved' && !log.resolved) return false - if (filterResolved === 'unresolved' && log.resolved) return false - return true - }) - - return ( -
-
- - - Total - - -
{stats.total}
-
-
- - - - Errors - - -
{stats.errors}
-
-
- - - - Warnings - - -
{stats.warnings}
-
-
- - - - Info - - -
{stats.info}
-
-
- - - - Resolved - - -
{stats.resolved}
-
-
- - - - Unresolved - - -
{stats.unresolved}
-
-
-
- - - -
-
- System Error Logs - - Track and manage system errors, warnings, and info messages - -
-
- - - -
-
- -
- - - -
-
- - -
- {filteredLogs.length === 0 && !loading && ( -
- No error logs found -
- )} - - {filteredLogs.map((log) => ( - - -
-
-
-
- {getLevelIcon(log.level)} -
- - {log.level.toUpperCase()} - - {log.resolved && ( - - - Resolved - - )} - - {new Date(log.timestamp).toLocaleString()} - -
- -
-

{log.message}

- {log.source && ( -

- Source: {log.source} -

- )} - {log.username && ( -

- User: {log.username} {log.userId && `(${log.userId})`} -

- )} -
- - {log.stack && ( -
- - Stack trace - -
-                              {log.stack}
-                            
-
- )} - - {log.context && ( -
- - Context - -
-                              {JSON.stringify(JSON.parse(log.context), null, 2)}
-                            
-
- )} - - {log.resolved && log.resolvedAt && ( -

- Resolved on {new Date(log.resolvedAt).toLocaleString()} - {log.resolvedBy && ` by ${log.resolvedBy}`} -

- )} -
- -
- {!log.resolved && ( - - )} - -
-
-
-
- ))} -
-
-
-
- - - - - - - Confirm Clear Error Logs - - - {clearOnlyResolved - ? 'This will permanently delete all resolved error logs. This action cannot be undone.' - : 'This will permanently delete ALL error logs. This action cannot be undone.'} - - - - - Cancel - - - Clear Logs - - - - -
- ) } diff --git a/frontends/nextjs/src/components/level5/tabs/PowerTransferTab.test.tsx b/frontends/nextjs/src/components/level5/tabs/__tests__/PowerTransferTab.test.tsx similarity index 97% rename from frontends/nextjs/src/components/level5/tabs/PowerTransferTab.test.tsx rename to frontends/nextjs/src/components/level5/tabs/__tests__/PowerTransferTab.test.tsx index 52e4470cf..33090cce7 100644 --- a/frontends/nextjs/src/components/level5/tabs/PowerTransferTab.test.tsx +++ b/frontends/nextjs/src/components/level5/tabs/__tests__/PowerTransferTab.test.tsx @@ -1,6 +1,6 @@ import { describe, it, expect, vi, afterEach } from 'vitest' import { render, screen, waitFor, fireEvent } from '@testing-library/react' -import { PowerTransferTab } from './PowerTransferTab' +import { PowerTransferTab } from '../PowerTransferTab' import type { User } from '@/lib/level-types' const superGodUser: User = { diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/ClearLogsDialog.tsx b/frontends/nextjs/src/components/level5/tabs/error-logs/ClearLogsDialog.tsx new file mode 100644 index 000000000..5050000ec --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/ClearLogsDialog.tsx @@ -0,0 +1,49 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui' +import { Warning } from '@phosphor-icons/react' + +interface ClearLogsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + clearOnlyResolved: boolean + onConfirm: () => void +} + +export function ClearLogsDialog({ open, onOpenChange, clearOnlyResolved, onConfirm }: ClearLogsDialogProps) { + return ( + + + + + + Confirm Clear Error Logs + + + {clearOnlyResolved + ? 'This will permanently delete all resolved error logs. This action cannot be undone.' + : 'This will permanently delete ALL error logs. This action cannot be undone.'} + + + + + Cancel + + + Clear Logs + + + + + ) +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogControls.tsx b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogControls.tsx new file mode 100644 index 000000000..2cec53e45 --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogControls.tsx @@ -0,0 +1,102 @@ +import { Badge, Button, CardDescription, CardTitle, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui' +import type { ErrorLevelFilter, ResolutionFilter } from './useErrorLogFilters' +import type { User } from '@/lib/level-types' +import { Broom } from '@phosphor-icons/react' + +interface ErrorLogControlsProps { + filterLevel: ErrorLevelFilter + filterResolution: ResolutionFilter + setFilterLevel: (value: ErrorLevelFilter) => void + setFilterResolution: (value: ResolutionFilter) => void + onRefresh: () => void + loading: boolean + user?: User + onRequestClear: (clearOnlyResolved: boolean) => void +} + +export function ErrorLogControls({ + filterLevel, + filterResolution, + setFilterLevel, + setFilterResolution, + onRefresh, + loading, + user, + onRequestClear, +}: ErrorLogControlsProps) { + const isSuperGod = user?.role === 'supergod' + const scopeDescription = isSuperGod + ? 'All error logs across all tenants' + : 'Error logs for your tenant only' + + return ( +
+
+ System Error Logs + + {scopeDescription} + {user?.tenantId && !isSuperGod && ( + + Tenant: {user.tenantId} + + )} + +
+ +
+
+ + {isSuperGod && ( + <> + + + + )} +
+ +
+ + + +
+
+
+ ) +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogList.tsx b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogList.tsx new file mode 100644 index 000000000..eee49eda1 --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogList.tsx @@ -0,0 +1,149 @@ +import { Badge, Button, Card, CardContent, ScrollArea } from '@/components/ui' +import { CheckCircle, Info, Trash, Warning } from '@phosphor-icons/react' +import type { ErrorLog } from '@/lib/db/error-logs' +import type { User } from '@/lib/level-types' + +interface ErrorLogListProps { + logs: ErrorLog[] + onResolve: (id: string) => void + onDelete: (id: string) => void + loading: boolean + user?: User +} + +const LEVEL_ICON = { + error: , + warning: , + info: , + default: , +} + +const LEVEL_COLOR: Record = { + error: 'bg-red-500/20 text-red-400 border-red-500/50', + warning: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50', + info: 'bg-blue-500/20 text-blue-400 border-blue-500/50', + default: 'bg-gray-500/20 text-gray-400 border-gray-500/50', +} + +const getLevelColor = (level: string) => LEVEL_COLOR[level] ?? LEVEL_COLOR.default +const getLevelIcon = (level: string) => LEVEL_ICON[level as keyof typeof LEVEL_ICON] ?? LEVEL_ICON.default + +export function ErrorLogList({ logs, onResolve, onDelete, loading, user }: ErrorLogListProps) { + const isSuperGod = user?.role === 'supergod' + + return ( + +
+ {logs.length === 0 && !loading && ( +
+ No error logs found +
+ )} + + {logs.map((log) => ( + + +
+
+
+
+ {getLevelIcon(log.level)} +
+ + {log.level.toUpperCase()} + + {log.resolved && ( + + + Resolved + + )} + + {new Date(log.timestamp).toLocaleString()} + + {isSuperGod && log.tenantId && ( + + Tenant: {log.tenantId} + + )} +
+ +
+

{log.message}

+ {log.source && ( +

+ Source: {log.source} +

+ )} + {log.username && ( +

+ User: {log.username} {log.userId && `(${log.userId})`} +

+ )} +
+ + {log.stack && ( +
+ + Stack trace + +
+                        {log.stack}
+                      
+
+ )} + + {log.context && ( +
+ + Context + +
+                        {JSON.stringify(JSON.parse(log.context), null, 2)}
+                      
+
+ )} + + {log.resolved && log.resolvedAt && ( +

+ Resolved on {new Date(log.resolvedAt).toLocaleString()} + {log.resolvedBy && ` by ${log.resolvedBy}`} +

+ )} +
+ +
+ {!log.resolved && ( + + )} + {isSuperGod && ( + + )} +
+
+
+
+ ))} +
+
+ ) +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogStats.tsx b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogStats.tsx new file mode 100644 index 000000000..c2cad2c15 --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/ErrorLogStats.tsx @@ -0,0 +1,28 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui' +import type { ErrorLogStats } from './useErrorLogs' + +const STAT_CONFIG: Array<{ key: keyof ErrorLogStats; label: string; color: string }> = [ + { key: 'total', label: 'Total', color: 'text-white' }, + { key: 'errors', label: 'Errors', color: 'text-red-400' }, + { key: 'warnings', label: 'Warnings', color: 'text-yellow-400' }, + { key: 'info', label: 'Info', color: 'text-blue-400' }, + { key: 'resolved', label: 'Resolved', color: 'text-green-400' }, + { key: 'unresolved', label: 'Unresolved', color: 'text-orange-400' }, +] + +export function ErrorLogStats({ stats }: { stats: ErrorLogStats }) { + return ( +
+ {STAT_CONFIG.map(({ key, label, color }) => ( + + + {label} + + +
{stats[key]}
+
+
+ ))} +
+ ) +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/errorLogActions.ts b/frontends/nextjs/src/components/level5/tabs/error-logs/errorLogActions.ts new file mode 100644 index 000000000..3b1461aca --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/errorLogActions.ts @@ -0,0 +1,42 @@ +import { Database } from '@/lib/database' +import type { User } from '@/lib/level-types' +import { toast } from 'sonner' + +export async function markErrorResolved(id: string, reload: () => Promise, user?: User) { + try { + await Database.updateErrorLog(id, { + resolved: true, + resolvedAt: Date.now(), + resolvedBy: user?.username || 'admin', + }) + await reload() + toast.success('Error log marked as resolved') + } catch (error) { + toast.error('Failed to update error log') + } +} + +export async function deleteErrorLog(id: string, reload: () => Promise) { + try { + await Database.deleteErrorLog(id) + await reload() + toast.success('Error log deleted') + } catch (error) { + toast.error('Failed to delete error log') + } +} + +export async function clearErrorLogs( + clearOnlyResolved: boolean, + reload: () => Promise, + onCleared?: () => void +) { + try { + const count = await Database.clearErrorLogs(clearOnlyResolved) + await reload() + toast.success(`Cleared ${count} error log${count !== 1 ? 's' : ''}`) + onCleared?.() + } catch (error) { + toast.error('Failed to clear error logs') + } +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogFilters.ts b/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogFilters.ts new file mode 100644 index 000000000..cc011d5f8 --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogFilters.ts @@ -0,0 +1,39 @@ +import { useState } from 'react' +import type { ErrorLog } from '@/lib/db/error-logs' + +export type ErrorLevelFilter = 'all' | 'error' | 'warning' | 'info' +export type ResolutionFilter = 'all' | 'resolved' | 'unresolved' + +export interface ErrorLogFilters { + level: ErrorLevelFilter + resolution: ResolutionFilter +} + +interface UseErrorLogFiltersReturn { + filters: ErrorLogFilters + setFilterLevel: (value: ErrorLevelFilter) => void + setFilterResolution: (value: ResolutionFilter) => void +} + +export function useErrorLogFilters(): UseErrorLogFiltersReturn { + const [filterLevel, setFilterLevel] = useState('all') + const [filterResolved, setFilterResolved] = useState('all') + + return { + filters: { + level: filterLevel, + resolution: filterResolved, + }, + setFilterLevel, + setFilterResolution: setFilterResolved, + } +} + +export function filterLogs(logs: ErrorLog[], filters: ErrorLogFilters): ErrorLog[] { + return logs.filter(log => { + if (filters.level !== 'all' && log.level !== filters.level) return false + if (filters.resolution === 'resolved' && !log.resolved) return false + if (filters.resolution === 'unresolved' && log.resolved) return false + return true + }) +} diff --git a/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogs.ts b/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogs.ts new file mode 100644 index 000000000..815835eeb --- /dev/null +++ b/frontends/nextjs/src/components/level5/tabs/error-logs/useErrorLogs.ts @@ -0,0 +1,76 @@ +import { useCallback, useEffect, useState } from 'react' +import { Database } from '@/lib/database' +import type { ErrorLog } from '@/lib/db/error-logs' +import type { User } from '@/lib/level-types' +import { toast } from 'sonner' + +export interface ErrorLogStats { + total: number + errors: number + warnings: number + info: number + resolved: number + unresolved: number +} + +interface UseErrorLogsReturn { + logs: ErrorLog[] + loading: boolean + stats: ErrorLogStats + reload: () => Promise + isSuperGod: boolean +} + +export function useErrorLogs(user?: User): UseErrorLogsReturn { + const [logs, setLogs] = useState([]) + const [loading, setLoading] = useState(false) + const [stats, setStats] = useState({ + total: 0, + errors: 0, + warnings: 0, + info: 0, + resolved: 0, + unresolved: 0, + }) + + const isSuperGod = user?.role === 'supergod' + const tenantId = user?.tenantId + + const calculateStats = useCallback((data: ErrorLog[]) => { + setStats({ + total: data.length, + errors: data.filter(l => l.level === 'error').length, + warnings: data.filter(l => l.level === 'warning').length, + info: data.filter(l => l.level === 'info').length, + resolved: data.filter(l => l.resolved).length, + unresolved: data.filter(l => !l.resolved).length, + }) + }, []) + + const loadLogs = useCallback(async () => { + setLoading(true) + try { + const options = isSuperGod ? {} : { tenantId } + const data = await Database.getErrorLogs(options) + setLogs(data) + calculateStats(data) + } catch (error) { + toast.error('Failed to load error logs') + console.error('Error loading logs:', error) + } finally { + setLoading(false) + } + }, [calculateStats, isSuperGod, tenantId]) + + useEffect(() => { + loadLogs() + }, [loadLogs]) + + return { + logs, + loading, + stats, + reload: loadLogs, + isSuperGod, + } +}