Files
PROJECT-CONTORL/frontend/src/components/ResourceHistory.tsx
beabigegg 4b7e523f84 feat: implement debug logging cleanup and i18n coverage proposals
## cleanup-debug-logging
- Create environment-aware logger utility (logger.ts)
- Replace 60+ console.log/error statements across 28 files
- Production: only warn/error logs visible
- Development: all log levels with prefixes

Updated files:
- Contexts: NotificationContext, ProjectSyncContext, AuthContext
- Components: GanttChart, CalendarView, ErrorBoundary, and 11 others
- Pages: Tasks, Projects, Dashboard, and 7 others
- Services: api.ts

## complete-i18n-coverage
- WeeklyReportPreview: all strings translated, dynamic locale
- ReportHistory: all strings translated, dynamic locale
- AuditPage: detail modal and verification modal translated
- WorkloadPage: error message translated

Locale files updated:
- en/common.json, zh-TW/common.json: reports section
- en/audit.json, zh-TW/audit.json: modal sections
- en/workload.json, zh-TW/workload.json: errors section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:37:29 +08:00

166 lines
4.1 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react'
import { auditService, AuditLog } from '../services/audit'
import { logger } from '../utils/logger'
interface ResourceHistoryProps {
resourceType: string
resourceId: string
title?: string
}
export function ResourceHistory({ resourceType, resourceId, title = 'Change History' }: ResourceHistoryProps) {
const [logs, setLogs] = useState<AuditLog[]>([])
const [loading, setLoading] = useState(true)
const [expanded, setExpanded] = useState(false)
const loadHistory = useCallback(async () => {
setLoading(true)
try {
const response = await auditService.getResourceHistory(resourceType, resourceId, 10)
setLogs(response.logs)
} catch (error) {
logger.error('Failed to load resource history:', error)
} finally {
setLoading(false)
}
}, [resourceType, resourceId])
useEffect(() => {
loadHistory()
}, [loadHistory])
const formatChanges = (changes: AuditLog['changes']): string => {
if (!changes || changes.length === 0) return ''
return changes.map(c => `${c.field}: ${c.old_value ?? 'null'}${c.new_value ?? 'null'}`).join(', ')
}
if (loading) {
return <div style={styles.loading}>Loading history...</div>
}
if (logs.length === 0) {
return <div style={styles.empty}>No change history available</div>
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setExpanded(!expanded)
}
}
return (
<div style={styles.container}>
<div
style={styles.header}
onClick={() => setExpanded(!expanded)}
role="button"
tabIndex={0}
onKeyDown={handleKeyDown}
aria-expanded={expanded}
aria-label={`${title}, ${logs.length} items`}
>
<span style={styles.title}>{title}</span>
<span style={styles.toggleIcon} aria-hidden="true">{expanded ? '▼' : '▶'}</span>
</div>
{expanded && (
<div style={styles.content}>
{logs.map((log) => (
<div key={log.id} style={styles.logItem}>
<div style={styles.logHeader}>
<span style={styles.eventType}>{log.event_type}</span>
<span style={styles.time}>
{new Date(log.created_at).toLocaleString()}
</span>
</div>
<div style={styles.logBody}>
<span style={styles.userName}>{log.user_name || 'System'}</span>
{log.changes && log.changes.length > 0 && (
<span style={styles.changes}>{formatChanges(log.changes)}</span>
)}
</div>
</div>
))}
</div>
)}
</div>
)
}
const styles: Record<string, React.CSSProperties> = {
container: {
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '1px solid #e9ecef',
marginTop: '16px',
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 16px',
cursor: 'pointer',
userSelect: 'none',
},
title: {
fontWeight: 600,
fontSize: '14px',
color: '#495057',
},
toggleIcon: {
fontSize: '12px',
color: '#6c757d',
},
content: {
borderTop: '1px solid #e9ecef',
padding: '8px',
maxHeight: '300px',
overflowY: 'auto',
},
logItem: {
padding: '8px 12px',
borderBottom: '1px solid #e9ecef',
},
logHeader: {
display: 'flex',
justifyContent: 'space-between',
marginBottom: '4px',
},
eventType: {
fontSize: '12px',
fontWeight: 600,
color: '#007bff',
},
time: {
fontSize: '11px',
color: '#6c757d',
},
logBody: {
display: 'flex',
flexDirection: 'column',
gap: '2px',
},
userName: {
fontSize: '12px',
color: '#495057',
},
changes: {
fontSize: '11px',
color: '#6c757d',
fontStyle: 'italic',
},
loading: {
padding: '16px',
textAlign: 'center',
color: '#6c757d',
},
empty: {
padding: '16px',
textAlign: 'center',
color: '#6c757d',
fontSize: '14px',
},
}
export default ResourceHistory