IntegrationTest / src /components /Records.jsx
Yingtao-Zheng's picture
Upload partially updated files
8bbb872
import React, { useState, useEffect, useRef } from 'react';
function Records() {
const [filter, setFilter] = useState('all');
const [sessions, setSessions] = useState([]);
const [loading, setLoading] = useState(false);
const chartRef = useRef(null);
// 格式化时间
const formatDuration = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}m ${secs}s`;
};
// 格式化日期
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
// 加载会话数据
const loadSessions = async (filterType) => {
setLoading(true);
try {
const response = await fetch(`/api/sessions?filter=${filterType}&limit=50`);
const data = await response.json();
setSessions(data);
drawChart(data);
} catch (error) {
console.error('Failed to load sessions:', error);
} finally {
setLoading(false);
}
};
// 绘制图表
const drawChart = (data) => {
const canvas = chartRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = 300;
// 清空画布
ctx.clearRect(0, 0, width, height);
if (data.length === 0) {
ctx.fillStyle = '#999';
ctx.font = '16px Nunito';
ctx.textAlign = 'center';
ctx.fillText('No data available', width / 2, height / 2);
return;
}
// 准备数据 (最多显示最近20个会话)
const displayData = data.slice(0, 20).reverse();
const padding = 50;
const chartWidth = width - padding * 2;
const chartHeight = height - padding * 2;
const barWidth = chartWidth / displayData.length;
// 找到最大值用于缩放
const maxScore = 1.0;
// 绘制坐标轴
ctx.strokeStyle = '#E0E0E0';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, height - padding);
ctx.lineTo(width - padding, height - padding);
ctx.stroke();
// 绘制Y轴刻度
ctx.fillStyle = '#666';
ctx.font = '12px Nunito';
ctx.textAlign = 'right';
for (let i = 0; i <= 4; i++) {
const y = height - padding - (chartHeight * i / 4);
const value = (maxScore * i / 4 * 100).toFixed(0);
ctx.fillText(value + '%', padding - 10, y + 4);
// 绘制网格线
ctx.strokeStyle = '#F0F0F0';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(width - padding, y);
ctx.stroke();
}
// 绘制柱状图
displayData.forEach((session, index) => {
const barHeight = (session.focus_score / maxScore) * chartHeight;
const x = padding + index * barWidth + barWidth * 0.1;
const y = height - padding - barHeight;
const barActualWidth = barWidth * 0.8;
// 根据分数设置颜色 - 使用蓝色主题
const score = session.focus_score;
let color;
if (score >= 0.8) color = '#4A90E2';
else if (score >= 0.6) color = '#5DADE2';
else if (score >= 0.4) color = '#85C1E9';
else color = '#AED6F1';
ctx.fillStyle = color;
ctx.fillRect(x, y, barActualWidth, barHeight);
// 绘制边框
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.strokeRect(x, y, barActualWidth, barHeight);
});
// 绘制图例
ctx.textAlign = 'left';
ctx.font = 'bold 14px Nunito';
ctx.fillStyle = '#4A90E2';
ctx.fillText('Focus Score by Session', padding, 30);
};
// 初始加载
useEffect(() => {
loadSessions(filter);
}, [filter]);
// 处理筛选按钮点击
const handleFilterClick = (filterType) => {
setFilter(filterType);
};
// 查看详情
const handleViewDetails = (sessionId) => {
// 这里可以实现查看详情的功能,比如弹窗显示该会话的详细信息
alert(`View details for session ${sessionId}\n(Feature can be extended later)`);
};
return (
<main id="page-d" className="page">
<h1 className="page-title">My Records</h1>
<div className="records-controls" style={{ display: 'flex', justifyContent: 'center', gap: '10px', marginBottom: '30px' }}>
<button
id="filter-today"
onClick={() => handleFilterClick('today')}
style={{
padding: '10px 30px',
borderRadius: '8px',
border: filter === 'today' ? 'none' : '2px solid #4A90E2',
background: filter === 'today' ? '#4A90E2' : 'transparent',
color: filter === 'today' ? 'white' : '#4A90E2',
fontSize: '14px',
fontWeight: '500',
cursor: 'pointer',
transition: 'all 0.3s'
}}
>
Today
</button>
<button
id="filter-week"
onClick={() => handleFilterClick('week')}
style={{
padding: '10px 30px',
borderRadius: '8px',
border: filter === 'week' ? 'none' : '2px solid #4A90E2',
background: filter === 'week' ? '#4A90E2' : 'transparent',
color: filter === 'week' ? 'white' : '#4A90E2',
fontSize: '14px',
fontWeight: '500',
cursor: 'pointer',
transition: 'all 0.3s'
}}
>
This Week
</button>
<button
id="filter-month"
onClick={() => handleFilterClick('month')}
style={{
padding: '10px 30px',
borderRadius: '8px',
border: filter === 'month' ? 'none' : '2px solid #4A90E2',
background: filter === 'month' ? '#4A90E2' : 'transparent',
color: filter === 'month' ? 'white' : '#4A90E2',
fontSize: '14px',
fontWeight: '500',
cursor: 'pointer',
transition: 'all 0.3s'
}}
>
This Month
</button>
<button
id="filter-all"
onClick={() => handleFilterClick('all')}
style={{
padding: '10px 30px',
borderRadius: '8px',
border: filter === 'all' ? 'none' : '2px solid #4A90E2',
background: filter === 'all' ? '#4A90E2' : 'transparent',
color: filter === 'all' ? 'white' : '#4A90E2',
fontSize: '14px',
fontWeight: '500',
cursor: 'pointer',
transition: 'all 0.3s'
}}
>
All Time
</button>
</div>
<div className="chart-container" style={{
background: 'white',
padding: '20px',
borderRadius: '10px',
marginBottom: '30px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<canvas ref={chartRef} id="focus-chart" style={{ width: '100%', height: '300px' }}></canvas>
</div>
<div className="sessions-list" style={{
background: 'white',
padding: '20px',
borderRadius: '10px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<h2 style={{ color: '#333', marginBottom: '20px', fontSize: '18px', fontWeight: '600' }}>Recent Sessions</h2>
{loading ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
Loading sessions...
</div>
) : sessions.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
No sessions found for this period.
</div>
) : (
<table id="sessions-table" style={{ width: '100%', borderCollapse: 'collapse', borderRadius: '10px', overflow: 'hidden' }}>
<thead>
<tr style={{ background: '#4A90E2' }}>
<th style={{ padding: '15px', textAlign: 'left', color: 'white', fontWeight: '600', fontSize: '14px' }}>Date</th>
<th style={{ padding: '15px', textAlign: 'center', color: 'white', fontWeight: '600', fontSize: '14px' }}>Duration</th>
<th style={{ padding: '15px', textAlign: 'center', color: 'white', fontWeight: '600', fontSize: '14px' }}>Focus Score</th>
<th style={{ padding: '15px', textAlign: 'center', color: 'white', fontWeight: '600', fontSize: '14px' }}>Action</th>
</tr>
</thead>
<tbody id="sessions-tbody">
{sessions.map((session, index) => (
<tr key={session.id} style={{
background: index % 2 === 0 ? '#f8f9fa' : 'white',
borderBottom: '1px solid #e9ecef'
}}>
<td style={{ padding: '15px', color: '#333', fontSize: '13px' }}>{formatDate(session.start_time)}</td>
<td style={{ padding: '15px', textAlign: 'center', color: '#333', fontSize: '13px' }}>{formatDuration(session.duration_seconds)}</td>
<td style={{ padding: '15px', textAlign: 'center' }}>
<span
style={{
color:
session.focus_score >= 0.8
? '#28a745'
: session.focus_score >= 0.6
? '#ffc107'
: session.focus_score >= 0.4
? '#fd7e14'
: '#dc3545',
fontWeight: '600',
fontSize: '13px'
}}
>
{(session.focus_score * 100).toFixed(1)}%
</span>
</td>
<td style={{ padding: '15px', textAlign: 'center' }}>
<button
onClick={() => handleViewDetails(session.id)}
style={{
padding: '6px 20px',
background: '#4A90E2',
border: 'none',
color: 'white',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '12px',
fontWeight: '500',
transition: 'background 0.3s'
}}
onMouseOver={(e) => e.target.style.background = '#357ABD'}
onMouseOut={(e) => e.target.style.background = '#4A90E2'}
>
View
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</main>
);
}
export default Records;