feat(#12): дневная цель — soft cap reps/день, после которого упражнение умолкает

This commit is contained in:
AnRil
2026-05-22 13:45:37 +07:00
parent 9e59be9cba
commit a6ae931461
6 changed files with 121 additions and 11 deletions

View File

@@ -13,6 +13,8 @@ type Draft = {
intervalMinutes: number
enabled: boolean
category: ReminderCategory
/** undefined = без дневной цели (только interval). */
dailyGoal?: number
}
const EMPTY: Draft = {
@@ -21,7 +23,8 @@ const EMPTY: Draft = {
icon: 'Activity',
intervalMinutes: 30,
enabled: true,
category: 'exercise'
category: 'exercise',
dailyGoal: undefined
}
type Props = {
@@ -48,7 +51,8 @@ export function ExerciseEditor({
icon: exercise.icon,
intervalMinutes: exercise.intervalMinutes,
enabled: exercise.enabled,
category: exercise.category ?? 'exercise'
category: exercise.category ?? 'exercise',
dailyGoal: exercise.dailyGoal
})
} else {
setDraft(EMPTY)
@@ -156,6 +160,39 @@ export function ExerciseEditor({
</Field>
</div>
<Field label={t('editor.field.daily_goal')}>
<div className="flex items-center gap-2">
<input
type="number"
min={1}
placeholder={t('editor.field.daily_goal.placeholder')}
value={draft.dailyGoal ?? ''}
onChange={(e) => {
const v = e.target.value
if (v === '') setDraft({ ...draft, dailyGoal: undefined })
else
setDraft({
...draft,
dailyGoal: Math.max(1, Number(v) || 1)
})
}}
className="ios-input font-mono-num flex-1"
/>
{draft.dailyGoal !== undefined && (
<button
type="button"
onClick={() => setDraft({ ...draft, dailyGoal: undefined })}
className="h-9 px-3 rounded-xl bg-surface-2 text-text/65 text-[13px] font-semibold hover:text-text"
>
{t('editor.field.daily_goal.clear')}
</button>
)}
</div>
<div className="text-[12px] text-text/55 mt-1.5 leading-snug">
{t('editor.field.daily_goal.hint')}
</div>
</Field>
<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

@@ -250,6 +250,11 @@ export const ru: Dict = {
'category.eyes.cta': 'Дай глазам отдохнуть',
'category.posture.cta': 'Проверь осанку',
'editor.field.category': 'Категория',
'editor.field.daily_goal': 'Дневная цель',
'editor.field.daily_goal.placeholder': 'без ограничения',
'editor.field.daily_goal.clear': 'Снять',
'editor.field.daily_goal.hint':
'Когда наберёшь столько повторений за день, напоминания этого упражнения умолкнут до завтра.',
// Reminder window
'reminder.kicker': 'Время тренировки',
@@ -547,6 +552,11 @@ export const en: Dict = {
'category.eyes.cta': 'Rest your eyes',
'category.posture.cta': 'Check your posture',
'editor.field.category': 'Category',
'editor.field.daily_goal': 'Daily goal',
'editor.field.daily_goal.placeholder': 'no limit',
'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.',
// Reminder window
'reminder.kicker': 'Workout time',

View File

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