feat: add SKIP button to Focus Mode

Focus Mode now shows a SKIP button that moves to the next task of the day
without completing the current one. Skipped task ids are tracked in
WeeklyView and filtered out of the focus queue, so completing a task still
picks the correct next one; the list is cleared when Focus Mode closes.
The timer resets on each new task, and the button is disabled when no other
task is left for today.

Also drops the standalone "Element Colors" and "Weekend Colors" sections from
Styling settings (those controls live in the CSS Variables panel since
1.113.1) and enlarges the CSS variable swatches.

v1.114.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-01 17:37:17 +02:00
parent 9ce5664621
commit 7e3e30ec83
6 changed files with 66 additions and 182 deletions

View File

@ -2,6 +2,19 @@
All notable changes to this project are documented in this file. All notable changes to this project are documented in this file.
## [1.114.0] - 2026-09-01
### Added
- Focus Mode: "SKIP" button to move on to the next task of the day without completing the
current one. Skipped tasks are remembered until Focus Mode is closed, and the timer
resets for each new task. The button is disabled when no other task is left for today.
### Changed
- Styling settings: removed the standalone "Element Colors" and "Weekend Colors" sections —
those controls now live only in the CSS Variables panel (added there in 1.113.1).
- Styling settings: larger, borderless color swatches and bigger labels in the CSS Variables
panel.
## [1.113.1] - 2026-07-08 ## [1.113.1] - 2026-07-08
### Changed ### Changed

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.104.1", "version": "1.114.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.104.1", "version": "1.114.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^2.11.1", "@auth/prisma-adapter": "^2.11.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.113.1", "version": "1.114.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -11,9 +11,11 @@ interface FocusModeOverlayProps {
duration: number; // in minutes duration: number; // in minutes
onClose: () => void; onClose: () => void;
onComplete: (taskId: string) => void; onComplete: (taskId: string) => void;
onSkip?: (taskId: string) => void;
hasNext?: boolean;
} }
export default function FocusModeOverlay({ task, duration, onClose, onComplete }: FocusModeOverlayProps) { export default function FocusModeOverlay({ task, duration, onClose, onComplete, onSkip, hasNext }: FocusModeOverlayProps) {
const [timeLeft, setTimeLeft] = useState(duration * 60); const [timeLeft, setTimeLeft] = useState(duration * 60);
const [isActive, setIsActive] = useState(false); const [isActive, setIsActive] = useState(false);
const [isCompleting, setIsCompleting] = useState(false); const [isCompleting, setIsCompleting] = useState(false);
@ -51,6 +53,12 @@ export default function FocusModeOverlay({ task, duration, onClose, onComplete }
setTimeLeft(duration * 60); setTimeLeft(duration * 60);
}; };
const handleSkip = () => {
if (!task || !onSkip) return;
onSkip(task.id);
handleReset(); // Fresh timer for the next task
};
const handleComplete = async () => { const handleComplete = async () => {
if (!task) return; if (!task) return;
setIsCompleting(true); setIsCompleting(true);
@ -127,6 +135,16 @@ export default function FocusModeOverlay({ task, duration, onClose, onComplete }
<button className="focus-btn secondary" onClick={handleReset}> <button className="focus-btn secondary" onClick={handleReset}>
RESET RESET
</button> </button>
{task && onSkip && (
<button
className="focus-btn secondary"
onClick={handleSkip}
disabled={!hasNext || isCompleting}
title={hasNext ? 'Skip to the next task' : 'No other task left today'}
>
SKIP
</button>
)}
{task && ( {task && (
<button className="focus-btn success" onClick={handleComplete} disabled={isCompleting}> <button className="focus-btn success" onClick={handleComplete} disabled={isCompleting}>
{isCompleting ? 'COMPLETING...' : 'COMPLETE TASK'} {isCompleting ? 'COMPLETING...' : 'COMPLETE TASK'}
@ -249,6 +267,13 @@ export default function FocusModeOverlay({ task, duration, onClose, onComplete }
.focus-btn.secondary:hover { .focus-btn.secondary:hover {
border-color: white; border-color: white;
} }
.focus-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.focus-btn:disabled:hover {
transform: none;
}
.focus-btn.success { .focus-btn.success {
background: #009a9a; /* Weekly Teal */ background: #009a9a; /* Weekly Teal */
color: white; color: white;

View File

@ -358,9 +358,9 @@ function CssVariablesEditor({
onChange={onChange} onChange={onChange}
style={{ style={{
width: "32px", width: "32px",
height: "22px", height: "32px",
cursor: "pointer", cursor: "pointer",
border: "1px solid var(--weekly-settings-input-border)", border: "none",
borderRadius: "4px", borderRadius: "4px",
background: "transparent", background: "transparent",
flexShrink: 0, flexShrink: 0,
@ -368,7 +368,7 @@ function CssVariablesEditor({
/> />
<span <span
style={{ style={{
fontSize: "0.72rem", fontSize: "0.85rem",
color: "var(--weekly-settings-label)", color: "var(--weekly-settings-label)",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
@ -3790,171 +3790,7 @@ function SettingsSidebar({
</div> </div>
</div> </div>
{/* Element Colors */} {/* CSS Variables (includes Element Colors / Weekend Highlight Colors groups) */}
<div
style={{
background: "var(--weekly-settings-item-bg)",
padding: "12px",
borderRadius: "8px",
marginBottom: "12px",
}}
>
<label
style={{
display: "block",
fontSize: "0.85rem",
fontWeight: 600,
color: "var(--weekly-settings-label)",
marginBottom: "8px",
}}
>
Element Colors
</label>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
}}
>
<div>
<label
style={{
display: "block",
fontSize: "0.75rem",
color: "var(--weekly-settings-label)",
marginBottom: "4px",
}}
>
Today Highlight
</label>
<input
type="color"
value={profile.todayHighlightColor || "#f0fafa"}
onChange={(e) =>
saveFieldDebounced("todayHighlightColor", e.target.value)
}
style={{
width: "100%",
height: "30px",
cursor: "pointer",
border: "none",
background: "transparent",
}}
/>
</div>
<div>
<label
style={{
display: "block",
fontSize: "0.75rem",
color: "var(--weekly-settings-label)",
marginBottom: "4px",
}}
>
Past Days
</label>
<input
type="color"
value={profile.pastDayColor || "#a6a6a7"}
onChange={(e) =>
saveFieldDebounced("pastDayColor", e.target.value)
}
style={{
width: "100%",
height: "30px",
cursor: "pointer",
border: "none",
background: "transparent",
}}
/>
</div>
</div>
</div>
{/* Weekend Colors */}
<div
style={{
background: "var(--weekly-settings-item-bg)",
padding: "12px",
borderRadius: "8px",
marginBottom: "12px",
}}
>
<label
style={{
display: "block",
fontSize: "0.85rem",
fontWeight: 600,
color: "var(--weekly-settings-label)",
marginBottom: "8px",
}}
>
Weekend Highlight Colors
</label>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
}}
>
<div>
<label
style={{
display: "block",
fontSize: "0.75rem",
color: "var(--weekly-settings-label)",
marginBottom: "4px",
}}
>
Saturday
</label>
<input
type="color"
value={profile.weekendColorSat || "#666666"}
onChange={(e) =>
saveFieldDebounced("weekendColorSat", e.target.value)
}
style={{
width: "100%",
height: "30px",
cursor: "pointer",
border: "none",
background: "transparent",
}}
/>
</div>
<div>
<label
style={{
display: "block",
fontSize: "0.75rem",
color: "var(--weekly-settings-label)",
marginBottom: "4px",
}}
>
Sunday
</label>
<input
type="color"
value={profile.weekendColorSun || "#dc2626"}
onChange={(e) =>
saveFieldDebounced("weekendColorSun", e.target.value)
}
style={{
width: "100%",
height: "30px",
cursor: "pointer",
border: "none",
background: "transparent",
}}
/>
</div>
</div>
</div>
{/* CSS Variables */}
<CssVariablesEditor profile={profile} setProfile={setProfile} saveSetting={saveSetting} /> <CssVariablesEditor profile={profile} setProfile={setProfile} saveSetting={saveSetting} />
{/* Custom CSS */} {/* Custom CSS */}

View File

@ -1250,6 +1250,7 @@ export default function WeeklyView() {
const [selectedTaskForRecurrence, setSelectedTaskForRecurrence] = const [selectedTaskForRecurrence, setSelectedTaskForRecurrence] =
useState<Task | null>(null); useState<Task | null>(null);
const [showFocusMode, setShowFocusMode] = useState(false); const [showFocusMode, setShowFocusMode] = useState(false);
const [focusSkippedIds, setFocusSkippedIds] = useState<string[]>([]);
const [showSchedule, setShowSchedule] = useState(true); const [showSchedule, setShowSchedule] = useState(true);
const [focusBreakDuration, setFocusBreakDuration] = useState(5); const [focusBreakDuration, setFocusBreakDuration] = useState(5);
const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long"); const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long");
@ -9382,9 +9383,8 @@ export default function WeeklyView() {
)} )}
{/* Focus Mode Overlay */} {/* Focus Mode Overlay */}
{ {
showFocusMode && ( showFocusMode && (() => {
<FocusModeOverlay const focusQueue = (() => {
task={(() => {
// Logic to find the "Next Task" // Logic to find the "Next Task"
// 1. Tasks for today with start time, sorted by time // 1. Tasks for today with start time, sorted by time
// 2. Tasks for today without start time, sorted by order // 2. Tasks for today without start time, sorted by order
@ -9424,13 +9424,23 @@ export default function WeeklyView() {
return a.order - b.order; return a.order - b.order;
}); });
return activeTasks.length > 0 ? activeTasks[0] : null; return activeTasks.filter((t) => !focusSkippedIds.includes(t.id));
})()} })();
duration={focusTimerDuration}
onClose={() => setShowFocusMode(false)} return (
onComplete={(taskId) => toggleTask(taskId)} <FocusModeOverlay
/> task={focusQueue.length > 0 ? focusQueue[0] : null}
) hasNext={focusQueue.length > 1}
onSkip={(taskId) => setFocusSkippedIds((prev) => [...prev, taskId])}
duration={focusTimerDuration}
onClose={() => {
setShowFocusMode(false);
setFocusSkippedIds([]);
}}
onComplete={(taskId) => toggleTask(taskId)}
/>
);
})()
} }
{/* Settings Sidebar */} {/* Settings Sidebar */}