Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11,088 changes: 11,088 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"keycode-js": "^0.0.4",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-router-dom": "^5.3.4",
"recompose": "^0.23.5"
},
"scripts": {
Expand Down
15 changes: 15 additions & 0 deletions src/components/hoc/connect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

/**
* A simple HOC to connect a component to the StateProvider.
* This HOC assumes that the component is wrapped within a StateProvider.
*/
const connect = (Component) => {
const ConnectedComponent = (props) => {
return <Component {...props} />;
};

return ConnectedComponent;
};

export default connect;
132 changes: 132 additions & 0 deletions src/components/ui/Analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, {Component} from 'react';

class Analytics extends Component {
// Calculate today's focus duration
getTodayFocusDuration() {
const today = new Date();
today.setHours(0, 0, 0, 0);

return this.props.data.focusSession.focusHistory
.filter(session => new Date(session.timestamp) >= today)
.reduce((total, session) => total + session.duration, 0);
}

// Calculate weekly focus trend
getWeeklyFocusTrend() {
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const today = new Date();
const weeklyData = Array(7).fill(0);

this.props.data.focusSession.focusHistory.forEach(session => {
const sessionDate = new Date(session.timestamp);
const daysAgo = Math.floor((today - sessionDate) / (1000 * 60 * 60 * 24));

if (daysAgo < 7) {
weeklyData[6 - daysAgo] += session.duration;
}
});

return daysOfWeek.map((day, index) => ({day, minutes: Math.round(weeklyData[index] / 60)}));
}

// Calculate completed tasks distribution
getCompletedTasksDistribution() {
const completedTasks = this.props.data.list.filter(todo => todo.completed);
const totalTasks = this.props.data.list.length;
const completedPercentage = totalTasks > 0 ? (completedTasks.length / totalTasks) * 100 : 0;

return {
completed: completedTasks.length,
total: totalTasks,
percentage: completedPercentage
};
}

// Format duration from seconds to hours and minutes
formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);

if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}

render() {
const todayFocus = this.getTodayFocusDuration();
const weeklyTrend = this.getWeeklyFocusTrend();
const taskDistribution = this.getCompletedTasksDistribution();

return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-md-10">
<div className="card">
<div className="card-header bg-primary text-white">
<h3 className="text-center">Productivity Analytics</h3>
</div>
<div className="card-body">
{/* Today's Focus */}
<div className="mb-5">
<h4>Today's Focus</h4>
<div className="alert alert-success">
<h2 className="mb-0">{this.formatDuration(todayFocus)}</h2>
</div>
</div>

{/* Weekly Focus Trend */}
<div className="mb-5">
<h4>Weekly Focus Trend</h4>
<div className="row">
{weeklyTrend.map((dayData, index) => (
<div key={index} className="col-12 mb-3">
<div className="d-flex justify-content-between">
<span>{dayData.day}</span>
<span>{dayData.minutes} min</span>
</div>
<div className="progress">
<div
className="progress-bar"
role="progressbar"
style={{width: `${(dayData.minutes / Math.max(...weeklyTrend.map(d => d.minutes))) * 100}%`}}
aria-valuenow={dayData.minutes}
aria-valuemin="0"
aria-valuemax={Math.max(...weeklyTrend.map(d => d.minutes))}
></div>
</div>
</div>
))}
</div>
</div>

{/* Completed Tasks */}
<div>
<h4>Completed Tasks</h4>
<div className="alert alert-info">
<h2 className="mb-0">{taskDistribution.completed} out of {taskDistribution.total} tasks</h2>
<div className="progress mt-3" style={{height: '30px'}}>
<div
className="progress-bar bg-success"
role="progressbar"
style={{width: `${taskDistribution.percentage}%`}}
aria-valuenow={taskDistribution.percentage}
aria-valuemin="0"
aria-valuemax="100"
>
{taskDistribution.percentage.toFixed(1)}%
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}

export default Analytics;
210 changes: 210 additions & 0 deletions src/components/ui/FocusTimer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import React, {Component} from 'react';

class FocusTimer extends Component {
constructor(props) {
super(props);
this.state = {
customDuration: 25
};
}

// Format time from seconds to MM:SS
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

// Calculate the circumference of the circle for the progress indicator
getCircleCircumference() {
const radius = 80;
return 2 * Math.PI * radius;
}

// Calculate the progress stroke dashoffset based on the remaining time
getProgressDashoffset() {
const circumference = this.getCircleCircumference();
const progress = (this.props.data.focusSession.timer / (25 * 60)) * circumference;
return circumference - progress;
}

// Handle time preset selection
handlePresetSelection(minutes) {
this.props.actions.setFocusTimer(minutes);
this.setState({customDuration: minutes});
}

// Handle custom duration input change
handleCustomDurationChange(e) {
this.setState({customDuration: e.target.value});
}

// Apply custom duration
applyCustomDuration() {
const duration = parseInt(this.state.customDuration, 10);
if (duration > 0 && duration <= 60) {
this.props.actions.setFocusTimer(duration);
}
}

// Handle focus session completion
handleCompleteSession() {
// Show confirmation dialog
const confirmComplete = window.confirm('Focus session completed! Would you like to mark the associated todo as complete?');
this.props.actions.completeFocusSession(confirmComplete);
}

// Check if timer has reached zero and show confirmation
componentDidUpdate(prevProps) {
if (prevProps.data.focusSession.timer > 0 && this.props.data.focusSession.timer <= 0 && !this.props.data.focusSession.isRunning) {
this.handleCompleteSession();
}
}

render() {
const {focusSession} = this.props.data;
const {startFocusSession, pauseFocusSession, stopFocusSession} = this.props.actions;
const currentTodo = this.props.data.list.find(todo => todo.id === focusSession.currentTodoId);

return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<div className="card-header bg-primary text-white">
<h3 className="text-center">Focus Session</h3>
</div>
<div className="card-body">
{/* Circle Timer */}
<div className="d-flex justify-content-center mb-4">
<div className="position-relative">
<svg width="200" height="200">
{/* Background circle */}
<circle
cx="100"
cy="100"
r="80"
stroke="#e0e0e0"
strokeWidth="10"
fill="none"
/>
{/* Progress circle */}
<circle
cx="100"
cy="100"
r="80"
stroke="#28a745"
strokeWidth="10"
fill="none"
strokeDasharray={this.getCircleCircumference()}
strokeDashoffset={this.getProgressDashoffset()}
transform="rotate(-90 100 100)"
strokeLinecap="round"
/>
</svg>
<div className="position-absolute top-50 start-50 translate-middle">
<h1 className="timer-display">{this.formatTime(focusSession.timer)}</h1>
</div>
</div>
</div>

{/* Time Presets */}
<div className="d-flex justify-content-center mb-4">
<div className="btn-group">
<button
className={`btn ${focusSession.timer === 25*60 ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => this.handlePresetSelection(25)}
>
25 min
</button>
<button
className={`btn ${focusSession.timer === 5*60 ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => this.handlePresetSelection(5)}
>
5 min
</button>
<button
className={`btn ${focusSession.timer === 15*60 ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => this.handlePresetSelection(15)}
>
15 min
</button>
</div>
</div>

{/* Custom Duration */}
<div className="d-flex justify-content-center mb-4">
<div className="input-group" style={{maxWidth: '200px'}}>
<input
type="number"
className="form-control"
min="1"
max="60"
placeholder="Custom"
value={this.state.customDuration}
onChange={(e) => this.handleCustomDurationChange(e)}
/>
<button className="btn btn-outline-secondary" onClick={() => this.applyCustomDuration()}>Set</button>
</div>
</div>

{/* Todo Selection */}
<div className="mb-4">
<label className="form-label">Select a Todo to focus on:</label>
<select
className="form-select"
value={focusSession.currentTodoId || ''}
onChange={(e) => this.props.actions.startFocusSession(parseInt(e.target.value, 10))}
>
<option value="">Choose a todo...</option>
{this.props.data.list.map(todo => (
<option key={todo.id} value={todo.id}>{todo.text}</option>
))}
</select>
</div>

{/* Current Focused Todo */}
{currentTodo && (
<div className="alert alert-info mb-4">
<strong>Current Focus:</strong> {currentTodo.text}
</div>
)}

{/* Control Buttons */}
<div className="d-flex justify-content-center">
{!focusSession.isRunning && !focusSession.isPaused && (
<button className="btn btn-success mx-2" onClick={() => focusSession.currentTodoId && startFocusSession(focusSession.currentTodoId)}>
Start
</button>
)}
{focusSession.isRunning && (
<button className="btn btn-warning mx-2" onClick={() => pauseFocusSession()}>
Pause
</button>
)}
{focusSession.isPaused && (
<button className="btn btn-success mx-2" onClick={() => focusSession.currentTodoId && startFocusSession(focusSession.currentTodoId)}>
Resume
</button>
)}
{(focusSession.isRunning || focusSession.isPaused) && (
<button className="btn btn-danger mx-2" onClick={() => stopFocusSession()}>
Stop
</button>
)}
{focusSession.isRunning && (
<button className="btn btn-primary mx-2" onClick={() => this.handleCompleteSession()}>
Complete
</button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}

export default FocusTimer;
Loading