← back to Dear Bubbe Admin
frontend/src/components/ReportsPage.js
406 lines
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const ReportsPage = () => {
const [reports, setReports] = useState([])
const [loading, setLoading] = useState(false)
const [selectedReport, setSelectedReport] = useState(null)
const [reportData, setReportData] = useState(null)
const generateReport = async (type, timeframe = '24h') => {
try {
setLoading(true)
let endpoint = `/api/reports/${type}`
if (timeframe) endpoint += `?timeframe=${timeframe}`
const response = await axios.get(endpoint)
if (response.data.success) {
setReportData(response.data.report)
setSelectedReport(type)
}
} catch (error) {
console.error(`Failed to generate ${type} report:`, error)
alert(`Failed to generate ${type} report`)
} finally {
setLoading(false)
}
}
const exportReport = async () => {
try {
await axios.post('/api/reports/export', {
reportType: selectedReport,
data: reportData
})
alert('Report exported successfully')
} catch (error) {
console.error('Failed to export report:', error)
alert('Failed to export report')
}
}
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
<div className="loading-spinner"></div>
<span style={{ marginLeft: '12px' }}>Generating report...</span>
</div>
)
}
return (
<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px'
}}>
<h2>Reports & Analytics</h2>
{selectedReport && reportData && (
<button onClick={exportReport} className="btn btn-primary">
Export Report
</button>
)}
</div>
{!selectedReport ? (
<div className="dashboard-grid">
<div className="card">
<div className="card-header">
<h3 className="card-title">Daily Report</h3>
</div>
<div className="card-content">
<p>Comprehensive daily activity summary including user engagement, conversations, and security events.</p>
<div style={{ marginTop: '16px' }}>
<button
onClick={() => generateReport('daily')}
className="btn btn-primary"
style={{ marginRight: '8px' }}
>
Generate Today
</button>
<button
onClick={() => {
const date = prompt('Enter date (YYYY-MM-DD):')
if (date) generateReport('daily', date)
}}
className="btn btn-secondary"
>
Custom Date
</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Security Report</h3>
</div>
<div className="card-content">
<p>Security incidents, threat analysis, and user behavior monitoring.</p>
<div style={{ marginTop: '16px', display: 'grid', gap: '8px' }}>
<button
onClick={() => generateReport('security', '24h')}
className="btn btn-danger"
>
Last 24 Hours
</button>
<button
onClick={() => generateReport('security', '7d')}
className="btn btn-danger"
>
Last 7 Days
</button>
<button
onClick={() => generateReport('security', '30d')}
className="btn btn-danger"
>
Last 30 Days
</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Weekly Summary</h3>
</div>
<div className="card-content">
<p>Weekly trends, user growth, and engagement patterns.</p>
<div style={{ marginTop: '16px' }}>
<button
onClick={() => generateReport('weekly')}
className="btn btn-primary"
>
This Week
</button>
</div>
</div>
</div>
</div>
) : (
<div>
<div style={{ marginBottom: '20px' }}>
<button
onClick={() => { setSelectedReport(null); setReportData(null); }}
className="btn btn-secondary"
>
← Back to Reports
</button>
</div>
{reportData && (
<div className="card">
<div className="card-header">
<h3 className="card-title">
{selectedReport.charAt(0).toUpperCase() + selectedReport.slice(1)} Report
</h3>
<div style={{ fontSize: '14px', color: '#64748b' }}>
Generated: {new Date(reportData.generatedAt).toLocaleString()}
</div>
</div>
<div className="card-content">
{selectedReport === 'daily' && (
<DailyReportView data={reportData} />
)}
{selectedReport === 'security' && (
<SecurityReportView data={reportData} />
)}
{selectedReport === 'weekly' && (
<WeeklyReportView data={reportData} />
)}
</div>
</div>
)}
</div>
)}
</div>
)
}
const DailyReportView = ({ data }) => (
<div>
<div className="stat-cards" style={{ marginBottom: '24px' }}>
<div className="stat-card">
<div className="stat-label">Total Interactions</div>
<div className="stat-value-large">{data.summary?.totalInteractions || 0}</div>
</div>
<div className="stat-card">
<div className="stat-label">New Users</div>
<div className="stat-value-large">{data.userActivity?.newRegistrations || 0}</div>
</div>
<div className="stat-card critical">
<div className="stat-label">Security Incidents</div>
<div className="stat-value-large">{data.summary?.securityIncidents || 0}</div>
</div>
<div className="stat-card">
<div className="stat-label">Engagement Rate</div>
<div className="stat-value-large">{(data.summary?.engagement || 0).toFixed(1)}</div>
</div>
</div>
<div className="dashboard-grid">
<div className="card">
<div className="card-header">
<h4>User Activity</h4>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Active Users:</span>
<strong>{data.userActivity?.activeUsers || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Total Sessions:</span>
<strong>{data.userActivity?.totalSessions || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Unique Users:</span>
<strong>{data.userActivity?.uniqueActiveUsers || 0}</strong>
</div>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h4>Conversations</h4>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Total:</span>
<strong>{data.conversations?.totalConversations || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Voice Used:</span>
<strong>{data.conversations?.voiceUsage || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Avg Response Time:</span>
<strong>{(data.conversations?.avgResponseTime || 0).toFixed(0)}ms</strong>
</div>
</div>
</div>
</div>
</div>
{data.topTopics && data.topTopics.length > 0 && (
<div className="card" style={{ marginTop: '24px' }}>
<div className="card-header">
<h4>Top Discussion Topics</h4>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '8px' }}>
{data.topTopics.map((topic, index) => (
<div key={index} style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{topic._id}</span>
<span className="badge badge-medium">{topic.count}</span>
</div>
))}
</div>
</div>
</div>
)}
</div>
)
const SecurityReportView = ({ data }) => (
<div>
<div className="stat-cards" style={{ marginBottom: '24px' }}>
<div className="stat-card critical">
<div className="stat-label">Total Alerts</div>
<div className="stat-value-large">{data.summary?.totalAlerts || 0}</div>
</div>
<div className="stat-card critical">
<div className="stat-label">Critical Incidents</div>
<div className="stat-value-large">{data.summary?.criticalIncidents || 0}</div>
</div>
<div className="stat-card warning">
<div className="stat-label">Repeat Offenders</div>
<div className="stat-value-large">{data.summary?.repeatOffenderCount || 0}</div>
</div>
<div className="stat-card">
<div className="stat-label">Users Blocked</div>
<div className="stat-value-large">{data.summary?.blockedUserCount || 0}</div>
</div>
</div>
{data.repeatOffenders && data.repeatOffenders.length > 0 && (
<div className="card" style={{ marginBottom: '24px' }}>
<div className="card-header">
<h4>Repeat Offenders</h4>
</div>
<div className="card-content">
<div className="table-container">
<table className="table">
<thead>
<tr>
<th>IP Address</th>
<th>Alert Count</th>
<th>Severities</th>
<th>Types</th>
</tr>
</thead>
<tbody>
{data.repeatOffenders.slice(0, 10).map((offender, index) => (
<tr key={index}>
<td style={{ fontFamily: 'monospace' }}>{offender._id}</td>
<td><span className="badge badge-critical">{offender.alertCount}</span></td>
<td style={{ fontSize: '12px' }}>
{offender.severities.join(', ')}
</td>
<td style={{ fontSize: '12px' }}>
{offender.types.join(', ')}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
{data.geoThreats && data.geoThreats.length > 0 && (
<div className="card">
<div className="card-header">
<h4>Geographic Threat Distribution</h4>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '8px' }}>
{data.geoThreats.slice(0, 10).map((geo, index) => (
<div key={index} style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{geo._id || 'Unknown'}</span>
<span className="badge badge-high">{geo.alerts} alerts</span>
</div>
))}
</div>
</div>
</div>
)}
</div>
)
const WeeklyReportView = ({ data }) => (
<div>
<div className="stat-cards" style={{ marginBottom: '24px' }}>
<div className="stat-card">
<div className="stat-label">Total Conversations</div>
<div className="stat-value-large">{data.weekTotals?.conversations || 0}</div>
</div>
<div className="stat-card">
<div className="stat-label">New Users</div>
<div className="stat-value-large">{data.weekTotals?.newUsers || 0}</div>
</div>
<div className="stat-card warning">
<div className="stat-label">Total Alerts</div>
<div className="stat-value-large">{data.weekTotals?.alerts || 0}</div>
</div>
<div className="stat-card">
<div className="stat-label">Sessions</div>
<div className="stat-value-large">{data.weekTotals?.sessions || 0}</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h4>Daily Breakdown</h4>
</div>
<div className="card-content">
<div className="table-container">
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Conversations</th>
<th>Sessions</th>
<th>Alerts</th>
</tr>
</thead>
<tbody>
{data.dailyBreakdown?.map((day, index) => (
<tr key={index}>
<td>{new Date(day.date).toLocaleDateString()}</td>
<td>{day.conversations}</td>
<td>{day.sessions}</td>
<td>
{day.alerts > 0 ? (
<span className="badge badge-warning">{day.alerts}</span>
) : (
day.alerts
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
export default ReportsPage