From 5741d55c0b69c083413180dc578f733877292651 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sun, 5 Apr 2026 09:58:28 +0900 Subject: [PATCH] =?UTF-8?q?Phase=208=20=EB=A7=88=EB=AC=B4=EB=A6=AC:=20Hist?= =?UTF-8?q?oryPage=20=EB=82=A0=EC=A7=9C=20=EA=B7=B8=EB=A3=B9=ED=95=91=20+?= =?UTF-8?q?=20AppConfig=20=EC=9D=8C=EC=84=B1=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=ED=95=84=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HistoryPage: 평면 리스트 → TODAY/YESTERDAY/날짜 그룹핑 (DashboardPage와 동일 패턴) - AppConfig: dictationEnabled, agentModeEnabled, handsFreeEnabled 필드 추가 - ConfigService: 기본값 설정 (dictation=true, agent=false, handsFree=false) - recording-tip: 에러 아이콘 색상 #D32F2F → #ef4444 (d3roPalette.tag.red) --- src/main/services/ConfigService.ts | 5 +- src/renderer/pages/HistoryPage.tsx | 126 ++++++++++++++------ src/renderer/popups/recording-tip/style.css | 2 +- src/shared/types.ts | 6 + 4 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/main/services/ConfigService.ts b/src/main/services/ConfigService.ts index 5f7e8a5..63a1df1 100644 --- a/src/main/services/ConfigService.ts +++ b/src/main/services/ConfigService.ts @@ -55,7 +55,10 @@ const CONFIG_DEFAULTS: AppConfig = { hotkeyEnabled: true, insertMethod: 'clipboard', autoInsert: true, - maxHistoryEntries: 1000 + maxHistoryEntries: 1000, + dictationEnabled: true, + agentModeEnabled: false, + handsFreeEnabled: false, } let store: ElectronStore | null = null diff --git a/src/renderer/pages/HistoryPage.tsx b/src/renderer/pages/HistoryPage.tsx index 50bc022..95c2f1e 100644 --- a/src/renderer/pages/HistoryPage.tsx +++ b/src/renderer/pages/HistoryPage.tsx @@ -1,7 +1,7 @@ // src/renderer/pages/HistoryPage.tsx -// 인스트루먼트 미학: MetalCard + PhosphorText + Led +// 인스트루먼트 미학: MetalCard + PhosphorText + Led + 날짜 그룹핑 -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, useMemo } from 'react' import { Box, TextField, IconButton, InputAdornment } from '@mui/material' import SearchIcon from '@mui/icons-material/Search' import DeleteIcon from '@mui/icons-material/Delete' @@ -10,10 +10,10 @@ import { MetalCard, PhosphorText, Led } from '../components/ds' import { d3roPalette, d3roFontMono } from '../theme' import type { HistoryEntry, HistoryPage as HistoryPageData } from '@shared/types' -const PAGE_SIZE = 20 +const PAGE_SIZE = 50 -function formatDate(ts: number): string { - return new Date(ts).toLocaleString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) +function formatTime(ts: number): string { + return new Date(ts).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }) } function formatDuration(sec: number): string { @@ -22,6 +22,24 @@ function formatDuration(sec: number): string { return `${m}:${s.toString().padStart(2, '0')}` } +function getDateKey(ts: number): string { + const d = new Date(ts) + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` +} + +function getDateLabel(ts: number): string { + const d = new Date(ts) + d.setHours(0, 0, 0, 0) + const today = new Date() + today.setHours(0, 0, 0, 0) + const yesterday = new Date(today) + yesterday.setDate(yesterday.getDate() - 1) + + if (d.getTime() === today.getTime()) return 'TODAY' + if (d.getTime() === yesterday.getTime()) return 'YESTERDAY' + return d.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' }).toUpperCase() +} + export function HistoryPage(): React.ReactElement { const [data, setData] = useState(null) const [search, setSearch] = useState('') @@ -31,13 +49,27 @@ export function HistoryPage(): React.ReactElement { setLoading(true) const result = search.trim() ? await window.electronAPI.history.search({ query: search, page: 0, pageSize: PAGE_SIZE }) - : await window.electronAPI.history.getAll({ page: 0, pageSize: PAGE_SIZE }) + : await window.electronAPI.history.getAll({ page: 0, pageSize: PAGE_SIZE, sortOrder: 'desc' }) if (result.success) setData(result.data) setLoading(false) }, [search]) useEffect(() => { loadData() }, [loadData]) + // 날짜별 그룹핑 + const groupedEntries = useMemo(() => { + if (!data) return [] + const groups: Record = {} + for (const entry of data.entries) { + const key = getDateKey(entry.createdAt) + if (!groups[key]) { + groups[key] = { label: getDateLabel(entry.createdAt), entries: [] } + } + groups[key].entries.push(entry) + } + return Object.values(groups) + }, [data]) + return ( @@ -69,40 +101,56 @@ export function HistoryPage(): React.ReactElement { ) : ( - - {data.entries.map((entry: HistoryEntry) => ( - - - - - - {entry.polishedText || entry.originalText} + groupedEntries.map((group) => ( + + {/* 날짜 구분자 */} + + + {group.label} + + + + {group.entries.length}건 + + + + {/* 항목 */} + + {group.entries.map((entry: HistoryEntry) => ( + + + + + + {entry.polishedText || entry.originalText} + + + {formatTime(entry.createdAt)} + {formatDuration(entry.duration)} + {entry.detectedLanguage && {entry.detectedLanguage.toUpperCase()}} + {entry.mode.toUpperCase()} + + + + navigator.clipboard.writeText(entry.polishedText || entry.originalText)} + sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}> + + + { window.electronAPI.history.delete({ id: entry.id }); loadData() }} + sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}> + + + - - {formatDate(entry.createdAt)} - {formatDuration(entry.duration)} - {entry.detectedLanguage && {entry.detectedLanguage.toUpperCase()}} - {entry.mode.toUpperCase()} - - - - navigator.clipboard.writeText(entry.polishedText || entry.originalText)} - sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}> - - - { window.electronAPI.history.delete({ id: entry.id }); loadData() }} - sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}> - - - - - - ))} - + + ))} + + + )) )} ) diff --git a/src/renderer/popups/recording-tip/style.css b/src/renderer/popups/recording-tip/style.css index 14798c3..e75b980 100644 --- a/src/renderer/popups/recording-tip/style.css +++ b/src/renderer/popups/recording-tip/style.css @@ -82,7 +82,7 @@ body { justify-content: center; width: 18px; height: 18px; - background: #D32F2F; + background: #ef4444; /* d3roPalette.tag.red */ color: white; border-radius: 50%; font-size: 12px; diff --git a/src/shared/types.ts b/src/shared/types.ts index 77c07c7..473894f 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -374,6 +374,12 @@ export interface AppConfig { insertMethod: 'clipboard' | 'keyboard' autoInsert: boolean maxHistoryEntries: number + /** 받아쓰기 모드 활성화 (hold-to-talk) */ + dictationEnabled: boolean + /** Agent 모드 활성화 (더블프레스, dictation 의존) */ + agentModeEnabled: boolean + /** 핸즈프리 모드 활성화 (토글) */ + handsFreeEnabled: boolean } export interface ConfigGetParams {