Dashboard.jsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import React, { useState, useEffect } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import { Row, Col, Card, Statistic, List, Tag, Button, Spin, Empty } from 'antd';
  4. import {
  5. CheckCircleOutlined,
  6. ClockCircleOutlined,
  7. ExclamationCircleOutlined,
  8. ScheduleOutlined,
  9. RobotOutlined
  10. } from '@ant-design/icons';
  11. import axios from 'axios';
  12. import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
  13. import { Doughnut } from 'react-chartjs-2';
  14. import MoodSelector from '../components/MoodSelector';
  15. ChartJS.register(ArcElement, Tooltip, Legend);
  16. const Dashboard = () => {
  17. const [loading, setLoading] = useState(true);
  18. const [stats, setStats] = useState({
  19. totalTasks: 0,
  20. completedTasks: 0,
  21. pendingTasks: 0,
  22. importanceCounts: [],
  23. });
  24. const [recentTasks, setRecentTasks] = useState([]);
  25. const [schedules, setSchedules] = useState([]);
  26. const [todayMood, setTodayMood] = useState(null);
  27. useEffect(() => {
  28. const fetchDashboardData = async () => {
  29. try {
  30. setLoading(true);
  31. // 获取任务数据
  32. const tasksRes = await axios.get('/api/tasks');
  33. const tasks = tasksRes.data;
  34. // 获取时间安排数据
  35. const schedulesRes = await axios.get('/api/schedules');
  36. const allSchedules = schedulesRes.data;
  37. // 获取心情数据
  38. const moodsRes = await axios.get('/api/moods');
  39. const moods = moodsRes.data;
  40. // 计算统计数据
  41. const completedTasks = tasks.filter(task => task.completed).length;
  42. const pendingTasks = tasks.filter(task => !task.completed).length;
  43. // 按重要性分组
  44. const importanceCounts = Array(5).fill(0);
  45. tasks.forEach(task => {
  46. if (task.importance >= 1 && task.importance <= 5) {
  47. importanceCounts[task.importance - 1]++;
  48. }
  49. });
  50. // 获取最近任务(按创建日期排序)
  51. const sortedTasks = [...tasks].sort((a, b) =>
  52. new Date(b.created_date) - new Date(a.created_date)
  53. ).slice(0, 5);
  54. // 当前星期几(1-5对应周一到周五)
  55. const today = new Date().getDay();
  56. const dayOfWeek = today === 0 ? 5 : today === 6 ? 5 : today;
  57. // 过滤出今天的时间安排
  58. const todaySchedules = allSchedules.filter(
  59. schedule => schedule.day_of_week === dayOfWeek
  60. ).sort((a, b) => a.time_slot.localeCompare(b.time_slot));
  61. // 查找今天的心情
  62. const todayStr = new Date().toISOString().split('T')[0];
  63. const todayMoodRecord = moods.find(mood => mood.date === todayStr);
  64. setStats({
  65. totalTasks: tasks.length,
  66. completedTasks,
  67. pendingTasks,
  68. importanceCounts,
  69. });
  70. setRecentTasks(sortedTasks);
  71. setSchedules(todaySchedules);
  72. setTodayMood(todayMoodRecord?.mood_value || null);
  73. } catch (error) {
  74. console.error('获取仪表盘数据失败:', error);
  75. } finally {
  76. setLoading(false);
  77. }
  78. };
  79. fetchDashboardData();
  80. }, []);
  81. // 饼图数据
  82. const chartData = {
  83. labels: ['非常低', '低', '中等', '高', '非常高'],
  84. datasets: [
  85. {
  86. data: stats.importanceCounts,
  87. backgroundColor: [
  88. '#e5e7eb',
  89. '#bfdbfe',
  90. '#c7d2fe',
  91. '#fef3c7',
  92. '#fee2e2',
  93. ],
  94. borderColor: [
  95. '#d1d5db',
  96. '#93c5fd',
  97. '#a5b4fc',
  98. '#fde68a',
  99. '#fca5a5',
  100. ],
  101. borderWidth: 1,
  102. },
  103. ],
  104. };
  105. // 获取重要性标签
  106. const getImportanceTag = (importance) => {
  107. const colors = ['default', 'blue', 'purple', 'warning', 'error'];
  108. const labels = ['非常低', '低', '中等', '高', '非常高'];
  109. return (
  110. <Tag color={colors[importance - 1] || 'default'}>
  111. {labels[importance - 1] || '未知'}
  112. </Tag>
  113. );
  114. };
  115. // 心情选择回调
  116. const handleMoodSelect = async (value) => {
  117. try {
  118. await axios.post('/api/moods', { mood_value: value });
  119. setTodayMood(value);
  120. } catch (error) {
  121. console.error('记录心情失败:', error);
  122. }
  123. };
  124. if (loading) {
  125. return (
  126. <div className="flex items-center justify-center h-64">
  127. <Spin size="large" />
  128. </div>
  129. );
  130. }
  131. return (
  132. <div className="fade-in">
  133. <h1 className="text-2xl font-bold mb-6">仪表盘</h1>
  134. {/* 统计卡片 */}
  135. <Row gutter={[16, 16]}>
  136. <Col xs={24} sm={8}>
  137. <Card className="hover-card">
  138. <Statistic
  139. title="总任务数"
  140. value={stats.totalTasks}
  141. prefix={<ScheduleOutlined />}
  142. />
  143. </Card>
  144. </Col>
  145. <Col xs={24} sm={8}>
  146. <Card className="hover-card">
  147. <Statistic
  148. title="已完成任务"
  149. value={stats.completedTasks}
  150. valueStyle={{ color: '#3f8600' }}
  151. prefix={<CheckCircleOutlined />}
  152. />
  153. </Card>
  154. </Col>
  155. <Col xs={24} sm={8}>
  156. <Card className="hover-card">
  157. <Statistic
  158. title="待完成任务"
  159. value={stats.pendingTasks}
  160. valueStyle={{ color: '#cf1322' }}
  161. prefix={<ClockCircleOutlined />}
  162. />
  163. </Card>
  164. </Col>
  165. </Row>
  166. {/* 今日心情 */}
  167. <Card title="今日心情" className="mt-6 hover-card">
  168. <MoodSelector value={todayMood} onChange={handleMoodSelect} />
  169. </Card>
  170. <Row gutter={[16, 16]} className="mt-6">
  171. {/* 最近任务 */}
  172. <Col xs={24} md={16}>
  173. <Card
  174. title="最近任务"
  175. className="hover-card"
  176. extra={<Link to="/tasks">查看全部</Link>}
  177. >
  178. {recentTasks.length > 0 ? (
  179. <List
  180. dataSource={recentTasks}
  181. renderItem={task => (
  182. <List.Item className="slide-in">
  183. <List.Item.Meta
  184. title={
  185. <div className="flex items-center">
  186. <span className={task.completed ? 'line-through text-gray-500' : ''}>
  187. {task.name}
  188. </span>
  189. <div className="ml-2">
  190. {getImportanceTag(task.importance)}
  191. {task.completed &&
  192. <Tag color="success\" className="ml-1">已完成</Tag>
  193. }
  194. </div>
  195. </div>
  196. }
  197. description={
  198. <div>
  199. <p className="text-gray-500">
  200. 预计完成时间: {task.expected_completion_date}
  201. </p>
  202. {task.notes && <p className="text-gray-500 truncate">{task.notes}</p>}
  203. </div>
  204. }
  205. />
  206. </List.Item>
  207. )}
  208. />
  209. ) : (
  210. <Empty description="暂无任务" />
  211. )}
  212. </Card>
  213. </Col>
  214. {/* 任务重要性分布 */}
  215. <Col xs={24} md={8}>
  216. <Card title="任务重要性分布" className="hover-card h-full">
  217. {stats.totalTasks > 0 ? (
  218. <div className="flex justify-center">
  219. <div style={{ width: 320, height: 320, margin: '0 auto' }}>
  220. <Doughnut data={chartData} width={320} height={320} />
  221. </div>
  222. </div>
  223. ) : (
  224. <Empty description="暂无数据" />
  225. )}
  226. </Card>
  227. </Col>
  228. </Row>
  229. {/* 今日安排 */}
  230. <Card
  231. title="今日安排"
  232. className="mt-6 hover-card"
  233. extra={<Link to="/schedule">查看全部</Link>}
  234. >
  235. {schedules.length > 0 ? (
  236. <List
  237. dataSource={schedules}
  238. renderItem={schedule => (
  239. <List.Item className="slide-in">
  240. <List.Item.Meta
  241. title={
  242. <div className="flex items-center">
  243. <span className={schedule.completed ? 'line-through text-gray-500' : ''}>
  244. {schedule.time_slot} - {schedule.activity}
  245. </span>
  246. {schedule.completed &&
  247. <Tag color="success" className="ml-2">已完成</Tag>
  248. }
  249. </div>
  250. }
  251. />
  252. </List.Item>
  253. )}
  254. />
  255. ) : (
  256. <Empty description="今日无安排" />
  257. )}
  258. </Card>
  259. {/* AI助手链接 */}
  260. <div className="mt-6 flex justify-center">
  261. <Link to="/ai-chat">
  262. <Button type="primary" icon={<RobotOutlined />} size="large">
  263. 咨询AI助手
  264. </Button>
  265. </Link>
  266. </div>
  267. </div>
  268. );
  269. };
  270. export default Dashboard;