Surveillance Frontend LogRocket

Exemples LogRocket pour la relecture de session, le suivi d'erreurs, la surveillance de performance et l'analyse du comportement utilisateur

Key Facts

Category
Developer Tools
Items
4
Format Families
sample

Sample Overview

Exemples LogRocket pour la relecture de session, le suivi d'erreurs, la surveillance de performance et l'analyse du comportement utilisateur This sample set belongs to Developer Tools and can be used to test related workflows inside Elysia Tools.

💻 LogRocket Hello World typescript

🟢 simple

Configuration de base LogRocket avec enregistrement de session et initialisation

⏱️ 10 min 🏷️ frontend, monitoring, analytics
Prerequisites: JavaScript basics, HTML/CSS, Browser APIs
// LogRocket Hello World - Basic Frontend Monitoring
// Simple session recording and error tracking setup

import LogRocket from 'logrocket'

// Initialize LogRocket with your application ID
LogRocket.init('your-app-id')

// Record basic user information
LogRocket.identify('user-123', {
  name: 'John Doe',
  email: '[email protected]',
  subscriptionType: 'premium'
})

// Track custom events
LogRocket.track('User signed up', {
  method: 'email',
  timestamp: new Date().toISOString()
})

// Console.log for debugging
console.log('LogRocket initialized and recording!')

// Handle errors automatically
window.addEventListener('error', (event) => {
  LogRocket.captureException(event.error)
})

// Track page navigation
window.addEventListener('popstate', () => {
  LogRocket.track('Page navigation', {
    url: window.location.href,
    timestamp: new Date().toISOString()
  })
})

💻 Relecture de Session Avancée typescript

🟡 intermediate ⭐⭐⭐

Enregistrement de session configurable avec événements personnalisés et filtres

⏱️ 25 min 🏷️ frontend, monitoring, analytics, performance
Prerequisites: LogRocket basics, DOM manipulation, Performance APIs, Event handling
// LogRocket Advanced Session Replay
// Comprehensive session recording with custom event tracking

import LogRocket from 'logrocket'
import LogRocketFuzzySearch from 'logrocket-fuzzy-search'

// Initialize with advanced configuration
LogRocket.init('your-app-id', {
  network: {
    requestSanitizer: (request) => {
      // Remove sensitive headers
      if (request.headers.authorization) {
        delete request.headers.authorization
      }
      if (request.body && request.body.password) {
        request.body.password = '***'
      }
      return request
    },
    responseSanitizer: (response) => {
      // Sanitize sensitive response data
      if (response.body && response.body.token) {
        response.body.token = '***'
      }
      return response
    }
  },
  console: {
    shouldAggregateConsoleLogs: true
  },
  dom: {
    baseHref: window.location.origin,
    inputSanitizer: true
  }
})

// Enable fuzzy search for text inputs
LogRocketFuzzySearch(LogRocket)

// Track form interactions
class FormTracker {
  constructor() {
    this.setupFormListeners()
  }

  private setupFormListeners() {
    // Track form submissions
    document.addEventListener('submit', (event) => {
      const form = event.target as HTMLFormElement
      LogRocket.track('Form submitted', {
        formId: form.id,
        formName: form.name,
        fields: this.getFormData(form)
      })
    })

    // Track field focus events
    document.addEventListener('focusin', (event) => {
      const element = event.target as HTMLElement
      if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
        LogRocket.track('Field focused', {
          fieldName: element.getAttribute('name'),
          fieldType: element.getAttribute('type'),
          fieldId: element.id
        })
      }
    })

    // Track validation errors
    document.addEventListener('invalid', (event) => {
      const element = event.target as HTMLInputElement
      LogRocket.track('Validation error', {
        fieldName: element.name,
        fieldType: element.type,
        validationMessage: element.validationMessage
      })
    })
  }

  private getFormData(form: HTMLFormElement): Record<string, any> {
    const formData = new FormData(form)
    const data: Record<string, any> = {}

    // Sanitize sensitive data
    for (const [key, value] of formData.entries()) {
      if (key.toLowerCase().includes('password') ||
          key.toLowerCase().includes('token') ||
          key.toLowerCase().includes('secret')) {
        data[key] = '***'
      } else {
        data[key] = value
      }
    }

    return data
  }
}

// Track user interactions with specific elements
class InteractionTracker {
  constructor() {
    this.setupClickTracking()
    this.setupScrollTracking()
    this.setupRageClickDetection()
  }

  private setupClickTracking() {
    document.addEventListener('click', (event) => {
      const element = event.target as HTMLElement
      const elementInfo = {
        tagName: element.tagName,
        className: element.className,
        id: element.id,
        textContent: element.textContent?.slice(0, 50),
        attributes: this.getElementAttributes(element)
      }

      LogRocket.track('Element clicked', elementInfo)
    })
  }

  private setupScrollTracking() {
    let lastScrollTop = 0
    let scrollTimeout: number

    window.addEventListener('scroll', () => {
      clearTimeout(scrollTimeout)
      scrollTimeout = window.setTimeout(() => {
        const scrollTop = window.pageYOffset || document.documentElement.scrollTop
        const scrollDirection = scrollTop > lastScrollTop ? 'down' : 'up'
        const scrollPercentage = Math.round(
          (scrollTop / (document.documentElement.scrollHeight - window.innerHeight)) * 100
        )

        LogRocket.track('Page scrolled', {
          direction: scrollDirection,
          percentage: scrollPercentage,
          scrollTop
        })

        lastScrollTop = scrollTop
      }, 100)
    })
  }

  private setupRageClickDetection() {
    const clicks: Array<{ time: number; x: number; y: number }> = []
    const RAGE_CLICK_THRESHOLD = 3
    const RAGE_CLICK_TIMEFRAME = 1000 // 1 second

    document.addEventListener('click', (event) => {
      const now = Date.now()
      const click = { time: now, x: event.clientX, y: event.clientY }

      // Remove old clicks
      const recentClicks = clicks.filter(c => now - c.time < RAGE_CLICK_TIMEFRAME)
      clicks.length = 0
      clicks.push(...recentClicks)
      clicks.push(click)

      if (clicks.length >= RAGE_CLICK_THRESHOLD) {
        LogRocket.track('Rage click detected', {
          clickCount: clicks.length,
          timeframe: RAGE_CLICK_TIMEFRAME,
          x: event.clientX,
          y: event.clientY,
          target: (event.target as HTMLElement).tagName
        })
      }
    })
  }

  private getElementAttributes(element: HTMLElement): Record<string, string> {
    const attrs: Record<string, string> = {}
    for (const attr of element.attributes) {
      if (!['id', 'class', 'style'].includes(attr.name)) {
        attrs[attr.name] = attr.value
      }
    }
    return attrs
  }
}

// Initialize trackers
const formTracker = new FormTracker()
const interactionTracker = new InteractionTracker()

// Track performance metrics
class PerformanceTracker {
  constructor() {
    this.trackPageLoad()
    this.trackResourceTiming()
  }

  private trackPageLoad() {
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming

      LogRocket.track('Page load completed', {
        loadTime: navigation.loadEventEnd - navigation.fetchStart,
        domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
        firstPaint: this.getFirstPaint(),
        firstContentfulPaint: this.getFirstContentfulPaint()
      })
    })
  }

  private trackResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.entryType === 'resource') {
          LogRocket.track('Resource loaded', {
            name: entry.name,
            duration: entry.duration,
            size: (entry as PerformanceResourceTiming).transferSize,
            type: this.getResourceType(entry.name)
          })
        }
      }
    })

    observer.observe({ entryTypes: ['resource'] })
  }

  private getFirstPaint(): number {
    const paintEntries = performance.getEntriesByType('paint')
    const firstPaint = paintEntries.find(entry => entry.name === 'first-paint')
    return firstPaint ? firstPaint.startTime : 0
  }

  private getFirstContentfulPaint(): number {
    const paintEntries = performance.getEntriesByType('paint')
    const fcp = paintEntries.find(entry => entry.name === 'first-contentful-paint')
    return fcp ? fcp.startTime : 0
  }

  private getResourceType(url: string): string {
    if (url.match(/\.(css)$/)) return 'css'
    if (url.match(/\.(js)$/)) return 'javascript'
    if (url.match(/\.(png|jpg|jpeg|gif|webp|svg)$/)) return 'image'
    if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font'
    return 'other'
  }
}

// Initialize performance tracker
const performanceTracker = new PerformanceTracker()

console.log('LogRocket advanced session replay initialized')

// Export for React usage
export { FormTracker, InteractionTracker, PerformanceTracker }

💻 Intégration React Error Boundary typescript

🟡 intermediate ⭐⭐⭐

React error boundary avec intégration LogRocket pour un suivi complet des erreurs

⏱️ 20 min 🏷️ react, frontend, error-handling, monitoring
Prerequisites: React basics, JavaScript error handling, LogRocket
// LogRocket React Error Boundary Integration
// Automatic error capturing and reporting in React applications

import React, { Component, ErrorInfo, ReactNode } from 'react'
import LogRocket from 'logrocket'

interface Props {
  children: ReactNode
  fallback?: ReactNode
  onError?: (error: Error, errorInfo: ErrorInfo) => void
}

interface State {
  hasError: boolean
  error?: Error
  errorInfo?: ErrorInfo
  retryCount: number
}

class LogRocketErrorBoundary extends Component<Props, State> {
  private maxRetries = 3

  constructor(props: Props) {
    super(props)
    this.state = {
      hasError: false,
      retryCount: 0
    }
  }

  static getDerivedStateFromError(error: Error): Partial<State> {
    return {
      hasError: true,
      error
    }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.setState({ errorInfo })

    // Log to LogRocket
    LogRocket.captureException(error, {
      tags: {
        component: 'ErrorBoundary',
        severity: 'critical'
      },
      extra: {
        errorInfo,
        componentStack: errorInfo.componentStack,
        retryCount: this.state.retryCount
      }
    })

    // Track error event
    LogRocket.track('React error caught', {
      errorMessage: error.message,
      errorName: error.name,
      componentStack: errorInfo.componentStack,
      retryCount: this.state.retryCount,
      userAgent: navigator.userAgent,
      url: window.location.href
    })

    // Call custom error handler
    if (this.props.onError) {
      this.props.onError(error, errorInfo)
    }
  }

  handleRetry = () => {
    if (this.state.retryCount < this.maxRetries) {
      this.setState(prevState => ({
        hasError: false,
        error: undefined,
        errorInfo: undefined,
        retryCount: prevState.retryCount + 1
      }))

      LogRocket.track('Error boundary retry', {
        retryCount: this.state.retryCount + 1,
        maxRetries: this.maxRetries
      })
    }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div style={{
          padding: '20px',
          border: '1px solid #ff6b6b',
          borderRadius: '8px',
          backgroundColor: '#ffe0e0',
          margin: '20px'
        }}>
          <h2 style={{ color: '#d63031' }}>Something went wrong</h2>
          <p>We're sorry, but something unexpected happened.</p>

          {this.state.retryCount < this.maxRetries ? (
            <button
              onClick={this.handleRetry}
              style={{
                padding: '10px 20px',
                backgroundColor: '#0984e3',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer',
                marginRight: '10px'
              }}
            >
              Try Again ({this.maxRetries - this.state.retryCount} attempts left)
            </button>
          ) : (
            <p>Please refresh the page or contact support if the problem persists.</p>
          )}

          <details style={{ marginTop: '20px' }}>
            <summary>Error Details</summary>
            <pre style={{
              fontSize: '12px',
              backgroundColor: '#f8f9fa',
              padding: '10px',
              overflow: 'auto',
              maxHeight: '200px'
            }}>
              {this.state.error?.stack}
            </pre>
          </details>
        </div>
      )
    }

    return this.props.children
  }
}

// Hook for functional components
function useLogRocketErrorBoundary() {
  const [error, setError] = React.useState<Error | null>(null)

  const captureError = React.useCallback((error: Error) => {
    setError(error)

    LogRocket.captureException(error, {
      tags: {
        component: 'useLogRocketErrorBoundary',
        severity: 'critical'
      }
    })

    LogRocket.track('Functional component error', {
      errorMessage: error.message,
      errorName: error.name,
      stack: error.stack,
      url: window.location.href
    })
  }, [])

  const resetError = React.useCallback(() => {
    setError(null)
    LogRocket.track('Error boundary reset')
  }, [])

  React.useEffect(() => {
    const handleError = (event: ErrorEvent) => {
      captureError(event.error)
    }

    const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
      captureError(new Error(event.reason))
    }

    window.addEventListener('error', handleError)
    window.addEventListener('unhandledrejection', handleUnhandledRejection)

    return () => {
      window.removeEventListener('error', handleError)
      window.removeEventListener('unhandledrejection', handleUnhandledRejection)
    }
  }, [captureError])

  return { error, captureError, resetError }
}

// Higher-order component for automatic error boundary
function withLogRocketErrorBoundary<P extends object>(
  Component: React.ComponentType<P>,
  options?: {
    fallback?: ReactNode
    onError?: (error: Error, errorInfo: ErrorInfo) => void
  }
) {
  return function WithErrorBoundary(props: P) {
    return (
      <LogRocketErrorBoundary
        fallback={options?.fallback}
        onError={options?.onError}
      >
        <Component {...props} />
      </LogRocketErrorBoundary>
    )
  }
}

// Usage example
const MyComponent = () => {
  const [shouldThrow, setShouldThrow] = React.useState(false)

  if (shouldThrow) {
    throw new Error('This is a test error!')
  }

  return (
    <div>
      <h1>My Component</h1>
      <button onClick={() => setShouldThrow(true)}>
        Throw Error
      </button>
    </div>
  )
}

// Wrap component with error boundary
const SafeComponent = withLogRocketErrorBoundary(MyComponent, {
  onError: (error, errorInfo) => {
    console.error('Custom error handler:', error, errorInfo)
  }
})

// Functional component with hook
const FunctionalComponent = () => {
  const { error, captureError, resetError } = useLogRocketErrorBoundary()

  const handleClick = () => {
    try {
      throw new Error('User triggered error!')
    } catch (err) {
      captureError(err as Error)
    }
  }

  if (error) {
    return (
      <div>
        <p>Error occurred: {error.message}</p>
        <button onClick={resetError}>Reset Error</button>
      </div>
    )
  }

  return <button onClick={handleClick}>Trigger Error</button>
}

export {
  LogRocketErrorBoundary,
  useLogRocketErrorBoundary,
  withLogRocketErrorBoundary,
  SafeComponent,
  FunctionalComponent
}

💻 Tableau de Bord Analytique Personnalisé typescript

🔴 complex ⭐⭐⭐⭐

Construire un tableau de bord analytique personnalisé en utilisant les données et insights LogRocket

⏱️ 45 min 🏷️ frontend, analytics, dashboard, monitoring
Prerequisites: React hooks, LogRocket API, Data visualization, State management
// LogRocket Custom Analytics Dashboard
// Real-time analytics dashboard using LogRocket session data

import React, { useState, useEffect, useCallback } from 'react'
import LogRocket from 'logrocket'

interface SessionData {
  id: string
  timestamp: number
  duration: number
  userId: string
  events: Array<{
    type: string
    timestamp: number
    data: any
  }>
  errors: Array<{
    message: string
    stack: string
    timestamp: number
  }>
  pageViews: Array<{
    url: string
    timestamp: number
    duration: number
  }>
}

interface AnalyticsMetrics {
  totalSessions: number
  averageSessionDuration: number
  errorRate: number
  topPages: Array<{ url: string; views: number }>
  commonErrors: Array<{ message: string; count: number }>
  userRetention: {
    day1: number
    day7: number
    day30: number
  }
}

const LogRocketAnalyticsDashboard: React.FC = () => {
  const [sessions, setSessions] = useState<SessionData[]>([])
  const [metrics, setMetrics] = useState<AnalyticsMetrics | null>(null)
  const [loading, setLoading] = useState(true)
  const [dateRange, setDateRange] = useState({
    start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
    end: new Date()
  })
  const [selectedSession, setSelectedSession] = useState<SessionData | null>(null)

  // Initialize LogRocket session tracking
  useEffect(() => {
    LogRocket.getSessionURL((sessionURL) => {
      if (sessionURL) {
        LogRocket.track('Analytics dashboard viewed', {
          sessionURL,
          timestamp: Date.now()
        })
      }
    })
  }, [])

  // Fetch session data from LogRocket API
  const fetchSessionData = useCallback(async () => {
    try {
      setLoading(true)

      // This would typically call your backend API that queries LogRocket
      // For demo purposes, we'll simulate the data structure
      const mockSessionData: SessionData[] = [
        {
          id: 'session-1',
          timestamp: Date.now() - 3600000,
          duration: 1800000, // 30 minutes
          userId: 'user-123',
          events: [
            { type: 'page_view', timestamp: Date.now() - 3600000, data: { url: '/home' } },
            { type: 'click', timestamp: Date.now() - 3500000, data: { element: 'button' } },
            { type: 'form_submit', timestamp: Date.now() - 3400000, data: { formId: 'contact' } }
          ],
          errors: [
            { message: 'TypeError: Cannot read property of undefined', stack: '...', timestamp: Date.now() - 3300000 }
          ],
          pageViews: [
            { url: '/home', timestamp: Date.now() - 3600000, duration: 600000 },
            { url: '/products', timestamp: Date.now() - 3000000, duration: 1200000 }
          ]
        }
      ]

      setSessions(mockSessionData)
      calculateMetrics(mockSessionData)
    } catch (error) {
      console.error('Failed to fetch session data:', error)
      LogRocket.captureException(error as Error)
    } finally {
      setLoading(false)
    }
  }, [dateRange])

  // Calculate analytics metrics
  const calculateMetrics = (sessionData: SessionData[]) => {
    const metrics: AnalyticsMetrics = {
      totalSessions: sessionData.length,
      averageSessionDuration: sessionData.reduce((acc, s) => acc + s.duration, 0) / sessionData.length,
      errorRate: sessionData.filter(s => s.errors.length > 0).length / sessionData.length,
      topPages: getTopPages(sessionData),
      commonErrors: getCommonErrors(sessionData),
      userRetention: calculateUserRetention(sessionData)
    }

    setMetrics(metrics)

    // Track metrics calculation event
    LogRocket.track('Analytics metrics calculated', {
      totalSessions: metrics.totalSessions,
      errorRate: metrics.errorRate,
      averageSessionDuration: metrics.averageSessionDuration
    })
  }

  // Get most viewed pages
  const getTopPages = (sessionData: SessionData[]) => {
    const pageViews: Record<string, number> = {}

    sessionData.forEach(session => {
      session.pageViews.forEach(view => {
        pageViews[view.url] = (pageViews[view.url] || 0) + 1
      })
    })

    return Object.entries(pageViews)
      .sort(([, a], [, b]) => b - a)
      .slice(0, 10)
      .map(([url, views]) => ({ url, views }))
  }

  // Get most common errors
  const getCommonErrors = (sessionData: SessionData[]) => {
    const errors: Record<string, number> = {}

    sessionData.forEach(session => {
      session.errors.forEach(error => {
        errors[error.message] = (errors[error.message] || 0) + 1
      })
    })

    return Object.entries(errors)
      .sort(([, a], [, b]) => b - a)
      .slice(0, 5)
      .map(([message, count]) => ({ message, count }))
  }

  // Calculate user retention metrics
  const calculateUserRetention = (sessionData: SessionData[]) => {
    // Simplified retention calculation
    const now = Date.now()
    const dayMs = 24 * 60 * 60 * 1000

    return {
      day1: Math.random() * 0.5 + 0.3, // 30-80%
      day7: Math.random() * 0.3 + 0.1, // 10-40%
      day30: Math.random() * 0.2 + 0.05 // 5-25%
    }
  }

  // Filter sessions by date range
  const getFilteredSessions = () => {
    return sessions.filter(session => {
      const sessionDate = new Date(session.timestamp)
      return sessionDate >= dateRange.start && sessionDate <= dateRange.end
    })
  }

  // Export session data
  const exportData = () => {
    const filteredSessions = getFilteredSessions()
    const dataStr = JSON.stringify(filteredSessions, null, 2)
    const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)

    const exportFileDefaultName = `logrocket-sessions-${new Date().toISOString().split('T')[0]}.json`

    const linkElement = document.createElement('a')
    linkElement.setAttribute('href', dataUri)
    linkElement.setAttribute('download', exportFileDefaultName)
    linkElement.click()

    LogRocket.track('Analytics data exported', {
      sessionCount: filteredSessions.length,
      dateRange
    })
  }

  useEffect(() => {
    fetchSessionData()
  }, [fetchSessionData])

  if (loading) {
    return <div>Loading analytics data...</div>
  }

  if (!metrics) {
    return <div>No data available</div>
  }

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
      <h1>LogRocket Analytics Dashboard</h1>

      {/* Date Range Selector */}
      <div style={{ marginBottom: '20px', display: 'flex', gap: '10px', alignItems: 'center' }}>
        <label>Date Range:</label>
        <input
          type="date"
          value={dateRange.start.toISOString().split('T')[0]}
          onChange={(e) => setDateRange(prev => ({ ...prev, start: new Date(e.target.value) }))}
        />
        <span>to</span>
        <input
          type="date"
          value={dateRange.end.toISOString().split('T')[0]}
          onChange={(e) => setDateRange(prev => ({ ...prev, end: new Date(e.target.value) }))}
        />
        <button onClick={fetchSessionData}>Refresh</button>
        <button onClick={exportData}>Export Data</button>
      </div>

      {/* Key Metrics */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '20px', marginBottom: '30px' }}>
        <div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
          <h3>Total Sessions</h3>
          <p style={{ fontSize: '24px', fontWeight: 'bold', color: '#0984e3' }}>{metrics.totalSessions}</p>
        </div>

        <div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
          <h3>Avg Session Duration</h3>
          <p style={{ fontSize: '24px', fontWeight: 'bold', color: '#00b894' }}>
            {Math.round(metrics.averageSessionDuration / 1000 / 60)}m
          </p>
        </div>

        <div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
          <h3>Error Rate</h3>
          <p style={{ fontSize: '24px', fontWeight: 'bold', color: '#d63031' }}>
            {(metrics.errorRate * 100).toFixed(1)}%
          </p>
        </div>

        <div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
          <h3>Day 1 Retention</h3>
          <p style={{ fontSize: '24px', fontWeight: 'bold', color: '#fdcb6e' }}>
            {(metrics.userRetention.day1 * 100).toFixed(0)}%
          </p>
        </div>
      </div>

      {/* Top Pages */}
      <div style={{ marginBottom: '30px' }}>
        <h2>Top Pages</h2>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ backgroundColor: '#f8f9fa' }}>
              <th style={{ padding: '10px', textAlign: 'left' }}>Page URL</th>
              <th style={{ padding: '10px', textAlign: 'left' }}>Views</th>
            </tr>
          </thead>
          <tbody>
            {metrics.topPages.map((page, index) => (
              <tr key={index}>
                <td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{page.url}</td>
                <td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{page.views}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Common Errors */}
      <div style={{ marginBottom: '30px' }}>
        <h2>Common Errors</h2>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ backgroundColor: '#f8f9fa' }}>
              <th style={{ padding: '10px', textAlign: 'left' }}>Error Message</th>
              <th style={{ padding: '10px', textAlign: 'left' }}>Count</th>
            </tr>
          </thead>
          <tbody>
            {metrics.commonErrors.map((error, index) => (
              <tr key={index}>
                <td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{error.message}</td>
                <td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{error.count}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Sessions List */}
      <div>
        <h2>Recent Sessions</h2>
        <div style={{ maxHeight: '300px', overflowY: 'auto' }}>
          {getFilteredSessions().map((session) => (
            <div
              key={session.id}
              style={{
                padding: '10px',
                border: '1px solid #ddd',
                borderRadius: '4px',
                marginBottom: '10px',
                cursor: 'pointer'
              }}
              onClick={() => setSelectedSession(session)}
            >
              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                <span><strong>Session:</strong> {session.id}</span>
                <span>{new Date(session.timestamp).toLocaleString()}</span>
              </div>
              <div>
                <small>Duration: {Math.round(session.duration / 1000 / 60)}m | Errors: {session.errors.length}</small>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Session Details Modal */}
      {selectedSession && (
        <div style={{
          position: 'fixed',
          top: 0,
          left: 0,
          right: 0,
          bottom: 0,
          backgroundColor: 'rgba(0,0,0,0.5)',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          zIndex: 1000
        }}>
          <div style={{
            backgroundColor: 'white',
            padding: '20px',
            borderRadius: '8px',
            maxWidth: '600px',
            maxHeight: '80vh',
            overflowY: 'auto'
          }}>
            <h2>Session Details</h2>
            <p><strong>Session ID:</strong> {selectedSession.id}</p>
            <p><strong>User ID:</strong> {selectedSession.userId}</p>
            <p><strong>Duration:</strong> {Math.round(selectedSession.duration / 1000 / 60)} minutes</p>

            <h3>Events</h3>
            <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
              {selectedSession.events.map((event, index) => (
                <div key={index} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
                  <small>{event.type} - {new Date(event.timestamp).toLocaleTimeString()}</small>
                </div>
              ))}
            </div>

            <button
              onClick={() => setSelectedSession(null)}
              style={{ marginTop: '20px', padding: '10px 20px', cursor: 'pointer' }}
            >
              Close
            </button>
          </div>
        </div>
      )}
    </div>
  )
}

export default LogRocketAnalyticsDashboard