feat(#2): адаптивный шедулер — сдвигает напоминания на «хорошие» часы из истории
This commit is contained in:
@@ -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) => (
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user