Skip to content

Conversation

@hotomato
Copy link

@hotomato hotomato commented Dec 1, 2025

Features:

  • Daily/Weekly/Monthly statistics views
  • Activity heatmap
  • Interruption tracking and analysis
  • Historical data visualization
  • Statistics drawer integration

Files:

  • New components: Stats-day, Stats-week, Stats-month, Stats-heatmap
  • New utilities: StatisticsAnalyzer, StatisticsStore
  • Updated Timer with interruption dialog
  • Documentation: STATISTICS_FEATURE.md, STATISTICS_QUICKSTART.md

Features:
- Daily/Weekly/Monthly statistics views
- Activity heatmap
- Interruption tracking and analysis
- Historical data visualization
- Statistics drawer integration

Files:
- New components: Stats-day, Stats-week, Stats-month, Stats-heatmap
- New utilities: StatisticsAnalyzer, StatisticsStore
- Updated Timer with interruption dialog
- Documentation: STATISTICS_FEATURE.md, STATISTICS_QUICKSTART.md
Copilot AI review requested due to automatic review settings December 1, 2025 12:50
Copilot finished reviewing on behalf of hotomato December 1, 2025 12:53
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive statistics visualization system for the Pomotroid application, adding data tracking and analysis capabilities to help users understand their productivity patterns. The implementation introduces session recording, multi-dimensional analytics (day/week/month/history), activity heatmaps, and interruption tracking.

Key changes:

  • Data persistence layer with JSON file storage and UUID-based session tracking
  • Analytics engine providing daily, weekly, monthly, and historical statistics
  • Five new Vue components for statistics visualization (day, week, month, history views, plus heatmap)
  • Timer integration with automatic session recording and interruption dialog
  • Achievement system with 8 milestone badges

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
src/renderer/utils/StatisticsStore.js New data persistence layer managing pomodoro session records with CRUD operations and JSON storage
src/renderer/utils/StatisticsAnalyzer.js New analytics service providing multi-dimensional statistical analysis (day/week/month/history/heatmap)
src/renderer/store/modules/Statistics.js New Vuex module managing statistics state and actions for session lifecycle
src/renderer/components/timer/Timer.vue Integrated session tracking: creates records on start, marks completion, triggers interrupt dialog on reset
src/renderer/components/statistics/Stats-day.vue Daily statistics view showing completed sessions, timeline, and metrics
src/renderer/components/statistics/Stats-week.vue Weekly statistics with bar chart, daily breakdown, and efficiency insights
src/renderer/components/statistics/Stats-month.vue Monthly calendar heatmap view with streak tracking and activity days
src/renderer/components/statistics/Stats-history.vue Historical overview with cumulative stats, heatmap, and achievement system
src/renderer/components/statistics/Stats-heatmap.vue 24×7 hour/day heatmap showing work intensity distribution
src/renderer/components/statistics/Stats-interruptions.vue Interruption analysis showing ranked disruption reasons
src/renderer/components/InterruptDialog.vue New modal dialog for recording interruption reasons with predefined options
src/renderer/components/drawer/Drawer-statistics.vue Statistics drawer component with tabbed navigation between views
src/renderer/components/drawer/Drawer.vue Registered new statistics drawer component
src/renderer/components/drawer/Drawer-menu.vue Added statistics menu icon/entry point
src/renderer/App.vue Added InterruptDialog component to app root
src/index.ejs Added Content Security Policy meta tag (includes unsafe-eval and unsafe-inline)
docs/STATISTICS_FEATURE.md Comprehensive feature documentation with technical details and usage instructions
docs/STATISTICS_QUICKSTART.md Quick start guide with testing procedures and debugging tips

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +323 to +341
while (true) {
const daySessions = this.store.getSessionsByDate(checkDate)
const hasCompletedWork = daySessions.some(s => s.type === 'work' && s.completed)

if (hasCompletedWork) {
streak++
checkDate.setDate(checkDate.getDate() - 1)
} else {
// 如果是今天没有记录,继续往前查
if (checkDate.toDateString() === today.toDateString()) {
checkDate.setDate(checkDate.getDate() - 1)
continue
}
break
}
}

return streak
}
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculateCurrentStreak() method contains an infinite while (true) loop that could potentially hang the application if there's an edge case not covered by the break conditions. Consider adding a safety counter or maximum iteration limit to prevent infinite loops.

Copilot uses AI. Check for mistakes.
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" />
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Content Security Policy (CSP) includes 'unsafe-eval' and 'unsafe-inline' for scripts, which significantly weakens the security benefits of CSP. These directives allow inline scripts and eval(), making the application vulnerable to XSS attacks. Consider refactoring to remove inline scripts and avoid eval() to strengthen security, or document why these exceptions are necessary.

Copilot uses AI. Check for mistakes.
Comment on lines +251 to +256
export default {
state,
getters,
mutations,
actions
}
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Vuex module doesn't specify namespaced: true, which means all getters, mutations, and actions are registered in the global namespace. This can lead to naming conflicts if other modules use the same names (e.g., currentSession, currentView). Consider adding namespaced: true to the module export or ensuring unique naming across all modules.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +60
saveData() {
try {
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2))
} catch (error) {
logger.error('Failed to save statistics data:', error)
}
}
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The saveData() method is called synchronously after every data modification (create, complete, delete, clear, import). If multiple operations happen in quick succession, this could lead to file system race conditions or data loss, especially on slower storage devices. Consider implementing a debounced save mechanism or using a queue to batch write operations.

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +4
const electron = require('electron')
const fs = require('fs')
const path = require('path')
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The require import style is inconsistent with the ES6 import syntax used at the top of the file. Consider using import electron from 'electron', import fs from 'fs', and import path from 'path' for consistency.

Suggested change
const electron = require('electron')
const fs = require('fs')
const path = require('path')
import electron from 'electron'
import fs from 'fs'
import path from 'path'

Copilot uses AI. Check for mistakes.
}
.BarChart-bar {
background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark, var(--color-accent)) 100%);
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The CSS custom property fallback var(--color-accent-dark, var(--color-accent)) will use --color-accent if --color-accent-dark is not defined. However, this component depends on --color-accent-dark existing. If it doesn't exist in the theme, the gradient will be monochrome. Consider defining a proper fallback color or documenting the theme requirements.

Suggested change
background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark, var(--color-accent)) 100%);
background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark, #005fa3) 100%);

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +206
importFromJSON(jsonString) {
try {
const importedData = JSON.parse(jsonString)
if (importedData.sessions && Array.isArray(importedData.sessions)) {
this.data = importedData
this.saveData()
logger.info('Data imported successfully')
}
} catch (error) {
logger.error('Failed to import data:', error)
throw new Error('Invalid JSON format')
}
}
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for importFromJSON. If the imported data contains sessions without required fields (e.g., missing id, type, or startTime), this could lead to runtime errors when the data is later used. Consider validating the structure and required fields of imported sessions before accepting the data.

Copilot uses AI. Check for mistakes.
})
EventBus.$on('call-timer-reset', () => {
this.resetTimer()
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resetTimer function is called with skipInterrupt = false by default at line 371, which will trigger the interrupt dialog every time the timer is reset, even when called via the 'call-timer-reset' event. This may not be the intended behavior for all reset scenarios. Consider reviewing if all reset paths should trigger the interrupt dialog.

Suggested change
this.resetTimer()
this.resetTimer(true)

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +276
background-color: rgba(var(--color-accent-rgb, 76, 175, 80), 0.3);
}
&--level-2 {
background-color: rgba(var(--color-accent-rgb, 76, 175, 80), 0.5);
}
&--level-3 {
background-color: rgba(var(--color-accent-rgb, 76, 175, 80), 0.75);
}
&--level-4 {
background-color: var(--color-accent);
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The heatmap uses rgba(var(--color-accent-rgb, 76, 175, 80), 0.3) syntax which expects the CSS variable --color-accent-rgb to be defined as RGB values without the rgb() wrapper. If this variable is not defined in the theme system, the fallback 76, 175, 80 will be used. Consider documenting this requirement or checking if --color-accent-rgb is consistently defined across all themes.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +74
// 创建最近7天的测试数据
const store = require('@/utils/StatisticsStore').getStatisticsStore()
const today = new Date()

for (let day = 0; day < 7; day++) {
const date = new Date(today)
date.setDate(today.getDate() - day)

// 每天随机创建3-8个番茄钟
const count = Math.floor(Math.random() * 6) + 3

for (let i = 0; i < count; i++) {
const hour = Math.floor(Math.random() * 12) + 8 // 8-20点
const session = {
id: Math.random().toString(36).substr(2, 9),
type: 'work',
duration: 25,
startTime: new Date(date.setHours(hour, 0, 0, 0)).toISOString(),
endTime: new Date(date.setHours(hour, 25, 0, 0)).toISOString(),
completed: Math.random() > 0.2, // 80%完成率
interrupted: Math.random() < 0.2,
interruptReason: Math.random() < 0.2 ? ['紧急事项', '会议', '电话'][Math.floor(Math.random() * 3)] : null,
taskName: null,
taskId: null
}
store.data.sessions.push(session)
}
}

store.saveData()
console.log('测试数据已生成!刷新统计页面查看。')
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quick start guide provides example code that directly manipulates the store's internal data structure (store.data.sessions.push(session)), bypassing the public API methods like createSession(). This violates encapsulation and could lead to data inconsistencies. The example should use the public API: store.createSession(sessionData) followed by store.completeSession(session.id, completed, interruptReason).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant