// src/renderer/pages/HistoryPage.tsx // High-End Agency Dictation History & Memory Timeline import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { Box, IconButton, Tooltip } from '@mui/material' import { Download, Trash2, Clock, Filter } from 'lucide-react' import { PhosphorText, TactileBadge, PhysicalButton } from '@d3ro/ui/components/ds' import { d3roPalette, d3roTypo } from '@d3ro/ui/theme' import { useI18n } from '@d3ro/i18n' import { getDateKey } from '../utils/formatters' import { EmptyStateCard, SearchInput, PageHeader, HistoryEntryCard } from '../components/shared' import type { HistoryEntry, HistoryPage as HistoryPageData, TagCount } from '@d3ro/core/types' const PAGE_SIZE = 50 export function HistoryPage(): React.ReactElement { const { t, formatRelativeDate } = useI18n() const [data, setData] = useState(null) const [search, setSearch] = useState('') const [loading, setLoading] = useState(true) const [allTags, setAllTags] = useState([]) const [activeTag, setActiveTag] = useState(null) const loadTags = useCallback(async () => { const result = await window.electronAPI.memo.getAllTags() if (result.success) setAllTags(result.data) }, []) const loadData = useCallback(async () => { setLoading(true) let result: { success: boolean; data: HistoryPageData } | { success: false; error: unknown } if (activeTag) { result = await window.electronAPI.memo.searchByTag({ tag: activeTag, page: 0, pageSize: PAGE_SIZE }) } else if (search.trim()) { result = await window.electronAPI.history.search({ query: search, page: 0, pageSize: PAGE_SIZE }) } else { result = await window.electronAPI.history.getAll({ page: 0, pageSize: PAGE_SIZE, sortOrder: 'desc' }) } if (result.success) setData(result.data) setLoading(false) }, [search, activeTag]) useEffect(() => { loadData() loadTags() const unsub = window.electronAPI.app.onDataChanged(() => { loadData() loadTags() }) return unsub }, [loadData, loadTags]) 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: formatRelativeDate(entry.createdAt), entries: [] } } groups[key].entries.push(entry) } return Object.values(groups) }, [data, formatRelativeDate]) const handleCopy = useCallback((text: string) => { navigator.clipboard.writeText(text) }, []) const isExportingRef = useRef(false) const isDeletingAllRef = useRef(false) const isDeletingSingleRef = useRef(false) const handleDelete = useCallback( async (id: string) => { if (isDeletingSingleRef.current) return isDeletingSingleRef.current = true try { await window.electronAPI.history.delete({ id }) loadData() } finally { isDeletingSingleRef.current = false } }, [loadData], ) const handleTagClick = useCallback((tag: string) => { setActiveTag((prev) => (prev === tag ? null : tag)) setSearch('') }, []) const handleExport = useCallback(async () => { if (isExportingRef.current) return isExportingRef.current = true try { await window.electronAPI.memo.export({ format: 'markdown' as const, tag: activeTag ?? undefined, }) } finally { isExportingRef.current = false } }, [activeTag]) const handleDeleteAll = useCallback(async () => { if (isDeletingAllRef.current) return isDeletingAllRef.current = true try { await window.electronAPI.history.deleteAll() loadData() } finally { isDeletingAllRef.current = false } }, [loadData]) return ( {/* Page Header */} {t('memo.export')} } /> {/* Search Input Bar */} {!activeTag && ( )} {/* Tag Filters Bar */} {allTags.length > 0 && ( {t('memo.tags') || 'TAGS'}: {allTags.map((tc) => { const isSelected = activeTag === tc.tag return ( handleTagClick(tc.tag)} sx={{ cursor: 'pointer' }} > #{tc.tag} ({tc.count}) ) })} {activeTag && ( setActiveTag(null)} sx={{ cursor: 'pointer' }} > {t('memo.clearFilter')} ✕ )} )} {loading ? ( {t('common.loading')} ) : !data || data.entries.length === 0 ? ( } /> ) : ( groupedEntries.map((group) => ( {/* Date Group Header */} {group.label} {t('history.count', { count: group.entries.length })} {/* Entries Grid */} {group.entries.map((entry: HistoryEntry) => ( ))} )) )} ) }