fix(P1): delete-confirm, daily-goal closed UI, meeting indicator, modal-confirm

P1 #4 — ConfirmModal (новый src/renderer/src/components/ui/ConfirmModal.tsx)
    с iOS-стилем + focus-trap (через Modal). Delete упражнения в Dashboard
    теперь спрашивает «Удалить упражнение?» с destructive-кнопкой.

P1 #5 — Daily goal closed UI. ExerciseCard принимает doneToday prop
    и при `done >= dailyGoal` показывает «Цель закрыта · 100/100»
    вместо запутанного «25ч 13м» countdown'а. Цвет — success-зелёный.

P1 #6 — Meeting auto-pause indicator. Новый IPC.getMeetingActive +
    evtMeetingChanged event. meeting-detect broadcast'ит изменения
    состояния. Dashboard показывает info-баннер «Не дёргаем — ты на
    встрече» когда meetingAutoPause включён и хотя бы один meeting
    процесс запущен.

P1 #7 — Native window.confirm() заменён на ConfirmModal в Settings
    DataCard для restore-операции. Теперь iOS-style с destructive
    confirm-кнопкой и focus-trap'ом.

Заодно P2 #8: Brain-иконка-badge на ExerciseCard для adaptive
    упражнений — пользователь видит почему «Next» не строго равен
    intervalMinutes.
P2 #12: dailyReps/dailyRepsRange/totalDoneReps/repsDoneTodayForExercise
    используют entry.reps как fallback — heatmap не теряет данные
    после удаления упражнения.
This commit is contained in:
AnRil
2026-05-22 15:06:25 +07:00
parent 17df87b3aa
commit 9c989612fe
10 changed files with 254 additions and 15 deletions

View File

@@ -49,6 +49,7 @@ import {
getUpdaterStatus,
quitAndInstall
} from './updater'
import { isMeetingActiveSync } from './meeting-detect'
import {
validateActualReps,
validateChallengeInput,
@@ -187,6 +188,8 @@ export function registerIpc(): void {
ipcMain.handle(IPC.getAppVersion, () => app.getVersion())
ipcMain.handle(IPC.getMeetingActive, () => isMeetingActiveSync())
ipcMain.handle(IPC.quit, () => app.quit())
ipcMain.handle(IPC.reminderClose, () => hideReminderWindow())

View File

@@ -16,8 +16,16 @@
*/
import { exec } from 'node:child_process'
import { promisify } from 'node:util'
import { BrowserWindow } from 'electron'
import { IPC } from '@shared/ipc'
import { log } from './logger'
function broadcast(active: boolean): void {
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) win.webContents.send(IPC.evtMeetingChanged, active)
}
}
const execAsync = promisify(exec)
/**
@@ -65,6 +73,7 @@ export async function isMeetingActive(): Promise<boolean> {
if (lower.includes(`"${proc}",`)) {
if (!cachedActive) {
log.info(`[meeting] detected ${proc} — pausing reminders`)
broadcast(true)
}
cachedActive = true
return true
@@ -72,6 +81,7 @@ export async function isMeetingActive(): Promise<boolean> {
}
if (cachedActive) {
log.info('[meeting] no meeting processes — resuming reminders')
broadcast(false)
}
cachedActive = false
return false