feat(#2): адаптивный шедулер — сдвигает напоминания на «хорошие» часы из истории

This commit is contained in:
AnRil
2026-05-22 13:52:38 +07:00
parent 81481f2131
commit a0b89ddf71
7 changed files with 152 additions and 9 deletions

View File

@@ -15,6 +15,7 @@ type Draft = {
category: ReminderCategory
/** undefined = без дневной цели (только interval). */
dailyGoal?: number
adaptive?: boolean
}
const EMPTY: Draft = {
@@ -24,7 +25,8 @@ const EMPTY: Draft = {
intervalMinutes: 30,
enabled: true,
category: 'exercise',
dailyGoal: undefined
dailyGoal: undefined,
adaptive: false
}
type Props = {
@@ -52,7 +54,8 @@ export function ExerciseEditor({
intervalMinutes: exercise.intervalMinutes,
enabled: exercise.enabled,
category: exercise.category ?? 'exercise',
dailyGoal: exercise.dailyGoal
dailyGoal: exercise.dailyGoal,
adaptive: exercise.adaptive ?? false
})
} else {
setDraft(EMPTY)
@@ -193,6 +196,25 @@ export function ExerciseEditor({
</div>
</Field>
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={draft.adaptive ?? false}
onChange={(e) =>
setDraft({ ...draft, adaptive: e.target.checked })
}
className="mt-0.5 w-4 h-4 accent-accent"
/>
<div>
<div className="text-[14px] font-semibold leading-tight">
{t('editor.field.adaptive.label')}
</div>
<div className="text-[12px] text-text/55 mt-1 leading-snug">
{t('editor.field.adaptive.hint')}
</div>
</div>
</label>
<Field label={t('editor.field.icon')}>
<div className="grid grid-cols-8 gap-2 max-h-44 overflow-y-auto p-2 rounded-2xl bg-surface-2">
{ICON_CHOICES.map((name) => (

View File

@@ -258,6 +258,9 @@ export const ru: Dict = {
'editor.field.daily_goal.clear': 'Снять',
'editor.field.daily_goal.hint':
'Когда наберёшь столько повторений за день, напоминания этого упражнения умолкнут до завтра.',
'editor.field.adaptive.label': 'Адаптивное расписание',
'editor.field.adaptive.hint':
'Шедулер изучает, в какие часы ты чаще делаешь упражнение, и сдвигает напоминания на удобное тебе время. Заработает после 10 событий в истории.',
// Reminder window
'reminder.kicker': 'Время тренировки',
@@ -563,6 +566,9 @@ export const en: Dict = {
'editor.field.daily_goal.clear': 'Clear',
'editor.field.daily_goal.hint':
'Once you hit this many reps in a day, this reminder goes quiet until tomorrow.',
'editor.field.adaptive.label': 'Adaptive scheduling',
'editor.field.adaptive.hint':
'Scheduler learns which hours you reliably do this exercise and shifts reminders into your good windows. Kicks in after 10 history events.',
// Reminder window
'reminder.kicker': 'Workout time',

View File

@@ -75,6 +75,7 @@ export default function Dashboard(): JSX.Element {
enabled: boolean
category: import('@shared/types').ReminderCategory
dailyGoal?: number
adaptive?: boolean
}): Promise<void> {
if (editing) await window.api.updateExercise(editing.id, draft)
else await window.api.addExercise(draft)