feat: add conversion history view with real-time updates

Features:
- Created ConversionHistory component showing recent conversions
- Displays input/output formats, file sizes, and timestamps
- Relative time display (e.g., "5 minutes ago", "2 hours ago")
- Clear all history functionality with confirmation
- Individual history item removal
- Real-time updates when new conversions complete
- Custom event system for same-page updates
- Storage event listener for cross-tab synchronization
- Empty state with helpful messaging
- Clean, organized card-based layout
- Responsive design with proper spacing

Technical improvements:
- Enhanced history storage to dispatch custom events
- History component auto-refreshes on new conversions
- Maintains up to 10 most recent conversions
- Integrated seamlessly into main page layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-17 13:18:02 +01:00
parent 67a1c47396
commit cbaa4361cd
3 changed files with 177 additions and 1 deletions

View File

@@ -0,0 +1,169 @@
'use client';
import * as React from 'react';
import { History, Trash2, ArrowRight, Clock } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { formatFileSize } from '@/lib/utils/fileUtils';
import { getHistory, clearHistory, removeHistoryItem } from '@/lib/storage/history';
import type { ConversionHistoryItem } from '@/types/conversion';
export function ConversionHistory() {
const [history, setHistory] = React.useState<ConversionHistoryItem[]>([]);
// Load history on mount and listen for updates
React.useEffect(() => {
const loadHistory = () => {
const items = getHistory();
setHistory(items);
};
loadHistory();
// Listen for storage changes (e.g., from other tabs)
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'convert-ui-history') {
loadHistory();
}
};
// Listen for custom event (same-page updates)
const handleHistoryUpdate = () => {
loadHistory();
};
window.addEventListener('storage', handleStorageChange);
window.addEventListener('conversionHistoryUpdated', handleHistoryUpdate);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('conversionHistoryUpdated', handleHistoryUpdate);
};
}, []);
const handleClearHistory = () => {
if (confirm('Are you sure you want to clear all conversion history?')) {
clearHistory();
setHistory([]);
}
};
const handleRemoveItem = (id: string) => {
removeHistoryItem(id);
setHistory((prev) => prev.filter((item) => item.id !== id));
};
const formatTimestamp = (timestamp: number) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
return date.toLocaleDateString();
};
if (history.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<History className="h-5 w-5" />
Conversion History
</CardTitle>
<CardDescription>Your recent conversions will appear here</CardDescription>
</CardHeader>
<CardContent>
<div className="text-center py-12 text-muted-foreground">
<History className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No conversion history yet</p>
<p className="text-sm mt-1">Convert some files to see them here</p>
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<History className="h-5 w-5" />
Conversion History
</CardTitle>
<CardDescription>
Recent conversions ({history.length} item{history.length > 1 ? 's' : ''})
</CardDescription>
</div>
<Button variant="outline" size="sm" onClick={handleClearHistory}>
<Trash2 className="h-4 w-4 mr-2" />
Clear All
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
{history.map((item) => (
<div
key={item.id}
className="border border-border rounded-lg p-4 hover:bg-muted/50 transition-colors"
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
{/* File conversion info */}
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-foreground truncate">
{item.inputFileName}
</span>
<ArrowRight className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<span className="text-sm font-medium text-foreground truncate">
{item.outputFileName}
</span>
</div>
{/* Format conversion */}
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-1">
<span className="px-2 py-0.5 bg-muted rounded">
{item.inputFormat}
</span>
<ArrowRight className="h-3 w-3" />
<span className="px-2 py-0.5 bg-muted rounded">
{item.outputFormat}
</span>
<span></span>
<span>{formatFileSize(item.fileSize)}</span>
</div>
{/* Timestamp */}
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
<span>{formatTimestamp(item.timestamp)}</span>
</div>
</div>
{/* Remove button */}
<Button
variant="ghost"
size="icon"
onClick={() => handleRemoveItem(item.id)}
className="flex-shrink-0"
>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Remove</span>
</Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}