My-Weekly-ToDo-List/src/components/FocusModeOverlay.tsx

265 lines
9.2 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
interface Task {
id: string;
title: string;
completed: boolean;
}
interface FocusModeOverlayProps {
task: Task | null;
duration: number; // in minutes
onClose: () => void;
onComplete: (taskId: string) => void;
}
export default function FocusModeOverlay({ task, duration, onClose, onComplete }: FocusModeOverlayProps) {
const [timeLeft, setTimeLeft] = useState(duration * 60);
const [isActive, setIsActive] = useState(false);
const [isCompleting, setIsCompleting] = useState(false);
const timerRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (isActive && timeLeft > 0) {
timerRef.current = setInterval(() => {
setTimeLeft((prev) => prev - 1);
}, 1000);
} else if (timeLeft === 0) {
if (timerRef.current) clearInterval(timerRef.current);
setIsActive(false);
// Play sound?
}
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, [isActive, timeLeft]);
const formatTime = (seconds: number) => {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
};
const handleToggleTimer = () => {
setIsActive(!isActive);
};
const handleReset = () => {
setIsActive(false);
setTimeLeft(duration * 60);
};
const handleComplete = async () => {
if (!task) return;
setIsCompleting(true);
// Small delay for animation
await new Promise(resolve => setTimeout(resolve, 500));
onComplete(task.id);
setIsCompleting(false);
handleReset(); // Reset timer for next task
};
// Calculate progress for circle
// Circumference = 2 * PI * r
// r = 120
const circumference = 2 * Math.PI * 120;
const progress = timeLeft / (duration * 60);
const dashoffset = circumference * (1 - progress);
return (
<div className="focus-overlay">
<button className="close-btn" onClick={onClose} title="Exit Focus Mode">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div className="focus-content">
<h2 className="focus-header">DO THIS NOW</h2>
{task ? (
<div className={`focus-task ${isCompleting ? 'completing' : ''}`}>
{task.title}
</div>
) : (
<div className="focus-task empty">
No tasks scheduled for today!
<div style={{ fontSize: '1rem', marginTop: '1rem', opacity: 0.6 }}>Time to relax or plan ahead.</div>
</div>
)}
<div className="timer-container">
<svg className="timer-svg" width="260" height="260">
<circle
className="timer-circle-bg"
stroke="#333"
strokeWidth="8"
fill="transparent"
r="120"
cx="130"
cy="130"
/>
<circle
className="timer-circle-fg"
stroke="white"
strokeWidth="8"
fill="transparent"
r="120"
cx="130"
cy="130"
style={{
strokeDasharray: circumference,
strokeDashoffset: dashoffset,
transition: 'stroke-dashoffset 1s linear'
}}
/>
</svg>
<div className="timer-text">{formatTime(timeLeft)}</div>
</div>
<div className="focus-controls">
<button className="focus-btn" onClick={handleToggleTimer}>
{isActive ? 'PAUSE' : 'START'}
</button>
<button className="focus-btn secondary" onClick={handleReset}>
RESET
</button>
{task && (
<button className="focus-btn success" onClick={handleComplete} disabled={isCompleting}>
{isCompleting ? 'COMPLETING...' : 'COMPLETE TASK'}
</button>
)}
</div>
</div>
<style jsx>{`
.focus-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #1a1a1a;
color: white;
z-index: 2000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: var(--font-inter, sans-serif);
animation: fadeIn 0.3s ease-out;
}
.close-btn {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: #666;
cursor: pointer;
padding: 10px;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
color: white;
background: rgba(255,255,255,0.1);
}
.focus-content {
text-align: center;
max-width: 600px;
width: 100%;
padding: 20px;
}
.focus-header {
font-size: 1rem;
letter-spacing: 0.2em;
color: #666;
margin-bottom: 2rem;
font-weight: 600;
}
.focus-task {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 3rem;
line-height: 1.2;
transition: all 0.5s ease;
}
.focus-task.empty {
font-size: 1.5rem;
font-weight: 500;
color: #999;
}
.focus-task.completing {
opacity: 0;
transform: scale(0.9);
}
.timer-container {
position: relative;
width: 260px;
height: 260px;
margin: 0 auto 3rem;
}
.timer-svg {
transform: rotate(-90deg);
}
.timer-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 3rem;
font-family: monospace;
font-weight: 600;
}
.focus-controls {
display: flex;
justify-content: center;
gap: 15px;
}
.focus-btn {
background: white;
color: black;
border: none;
padding: 12px 24px;
font-size: 0.9rem;
font-weight: 600;
border-radius: 30px;
cursor: pointer;
min-width: 100px;
transition: transform 0.1s;
letter-spacing: 0.05em;
}
.focus-btn:hover {
transform: scale(1.05);
}
.focus-btn:active {
transform: scale(0.95);
}
.focus-btn.secondary {
background: transparent;
color: white;
border: 1px solid #666;
}
.focus-btn.secondary:hover {
border-color: white;
}
.focus-btn.success {
background: #009a9a; /* Weekly Teal */
color: white;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.98); }
to { opacity: 1; transform: scale(1); }
}
`}</style>
</div>
);
}