feat(web): recurring timer badge, recurringTimerId field, generateTimerFromRecurrence helper
This commit is contained in:
parent
d909830fcd
commit
a2e8f985d2
@ -103,6 +103,15 @@ export function TimerCard({ timer }: TimerCardProps) {
|
|||||||
<Tag size={10} /> {timer.category}
|
<Tag size={10} /> {timer.category}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{timer.recurringTimerId && (
|
||||||
|
<span
|
||||||
|
className="text-xs px-1.5 py-0.5 rounded-full flex items-center gap-0.5"
|
||||||
|
style={{ backgroundColor: 'rgba(46,230,214,0.15)', color: 'var(--cm-accent-secondary)' }}
|
||||||
|
title="Recurring timer"
|
||||||
|
>
|
||||||
|
<Repeat size={10} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{timer.linkedTimerId && (
|
{timer.linkedTimerId && (
|
||||||
<span
|
<span
|
||||||
className="text-xs px-1.5 py-0.5 rounded-full"
|
className="text-xs px-1.5 py-0.5 rounded-full"
|
||||||
|
|||||||
@ -240,6 +240,53 @@ export function createCustomRule(
|
|||||||
return { frequency: 'custom', daysOfWeek, timeOfDay: timeOfDayMinutes, endDate };
|
return { frequency: 'custom', daysOfWeek, timeOfDay: timeOfDayMinutes, endDate };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Timer Generation ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Timer from a RecurringTimer for the next occurrence.
|
||||||
|
* The generated timer has `recurringTimerId` set for badge display.
|
||||||
|
*/
|
||||||
|
export function generateTimerFromRecurrence(
|
||||||
|
recurring: RecurringTimer,
|
||||||
|
label: string,
|
||||||
|
urgency: import('./urgency').UrgencyLevel = 'standard',
|
||||||
|
category?: string,
|
||||||
|
): import('./timer-engine').Timer | null {
|
||||||
|
if (recurring.paused) return null;
|
||||||
|
|
||||||
|
const afterDate = recurring.skipNext
|
||||||
|
? getOccurrenceAfterSkip(recurring.recurrence, recurring.lastOccurrence ?? Date.now())
|
||||||
|
: getNextOccurrence(recurring.recurrence, recurring.lastOccurrence ?? Date.now());
|
||||||
|
|
||||||
|
if (!afterDate) return null;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const id = `${recurring.id}-${afterDate}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
type: 'alarm',
|
||||||
|
label,
|
||||||
|
urgency,
|
||||||
|
state: 'active',
|
||||||
|
targetTime: afterDate,
|
||||||
|
duration: null,
|
||||||
|
createdAt: now,
|
||||||
|
startedAt: now,
|
||||||
|
pausedAt: null,
|
||||||
|
firedAt: null,
|
||||||
|
dismissedAt: null,
|
||||||
|
completedAt: null,
|
||||||
|
elapsedBeforePause: 0,
|
||||||
|
cascade: { preset: 'standard', intervals: [] },
|
||||||
|
warnings: [],
|
||||||
|
snoozeCount: 0,
|
||||||
|
snoozedUntil: null,
|
||||||
|
category,
|
||||||
|
recurringTimerId: recurring.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ── Display Helpers ───────────────────────────────────────────
|
// ── Display Helpers ───────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -57,6 +57,7 @@ export interface Timer {
|
|||||||
category?: string;
|
category?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
linkedTimerId?: string | null;
|
linkedTimerId?: string | null;
|
||||||
|
recurringTimerId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PomodoroConfig {
|
export interface PomodoroConfig {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user