// src/renderer/pages/HistoryPage.tsx // 인스트루먼트 미학: MetalCard + PhosphorText + Led + 날짜 그룹핑 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' import ContentCopyIcon from '@mui/icons-material/ContentCopy' import { MetalCard, PhosphorText, Led } from '../components/ds' import { d3roPalette, d3roFontMono } from '../theme' import type { HistoryEntry, HistoryPage as HistoryPageData } from '@shared/types' const PAGE_SIZE = 50 function formatTime(ts: number): string { return new Date(ts).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }) } function formatDuration(sec: number): string { const m = Math.floor(sec / 60) const s = Math.round(sec % 60) 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('') const [loading, setLoading] = useState(true) const loadData = useCallback(async () => { 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, sortOrder: 'desc' }) if (result.success) setData(result.data) setLoading(false) }, [search]) useEffect(() => { loadData() const unsub = window.electronAPI.app.onDataChanged(() => { loadData() }) return unsub }, [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 ( TRANSCRIPTION LOG — {data?.total ?? 0} ENTRIES setSearch(e.target.value)} fullWidth sx={{ mb: 3, '& .MuiInputBase-input': { fontFamily: d3roFontMono, fontSize: '12px', letterSpacing: '0.5px' }, }} slotProps={{ input: { startAdornment: , }, }} /> {loading ? ( LOADING... ) : !data || data.entries.length === 0 ? ( {search ? 'NO RESULTS' : 'NO HISTORY — START RECORDING'} ) : ( 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 } }}> ))} )) )} ) }