feat(web): wire routine export/import into history page UI
This commit is contained in:
parent
77254b751e
commit
141fcc2a38
@ -7,7 +7,8 @@ import { computeStreak } from '@/lib/stats';
|
||||
import { StatsView } from '@/components/StatsView';
|
||||
import { StreakCard } from '@/components/StreakCard';
|
||||
import { TimerCard } from '@/components/TimerCard';
|
||||
import { downloadExport, readFileAsText, parseImportData, importTimers } from '@/lib/export';
|
||||
import { downloadExportAll, readFileAsText, parseImportData, importTimers, importRoutines } from '@/lib/export';
|
||||
import { useRoutineStore } from '@/lib/routine-store';
|
||||
import { importCalendar } from '@/lib/calendar-import';
|
||||
import { ArrowLeft, Download, Upload, Calendar, Search, Filter, RotateCcw, FileSpreadsheet, Eye, Check, X } from 'lucide-react';
|
||||
import { getCategoryById, getAllCategories } from '@/lib/categories';
|
||||
@ -17,6 +18,7 @@ import type { Timer } from '@/lib/timer-engine';
|
||||
export default function HistoryPage() {
|
||||
const timers = useTimerStore((s) => s.timers);
|
||||
const now = useTimerStore((s) => s.now);
|
||||
const routines = useRoutineStore((s) => s.routines);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [filterCategory, setFilterCategory] = useState<string | ''>('');
|
||||
@ -50,7 +52,7 @@ export default function HistoryPage() {
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
const handleExport = () => downloadExport(timers);
|
||||
const handleExport = () => downloadExportAll(timers, routines);
|
||||
|
||||
const handleJsonImport = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
@ -62,14 +64,28 @@ export default function HistoryPage() {
|
||||
setImportStatus('Invalid file format. Expected a ChronoMind export JSON.');
|
||||
return;
|
||||
}
|
||||
const result = importTimers(data, timers);
|
||||
const timerResult = importTimers(data, timers);
|
||||
// Add the imported timers to the store
|
||||
const existingIds = new Set(timers.map((t) => t.id));
|
||||
const newTimers = data.timers.filter((t) => !existingIds.has(t.id));
|
||||
if (newTimers.length > 0) {
|
||||
useTimerStore.setState((s) => ({ timers: [...s.timers, ...newTimers] }));
|
||||
}
|
||||
setImportStatus(`Imported ${result.imported} timers. ${result.skipped} skipped.${result.errors.length > 0 ? ` Errors: ${result.errors.join(', ')}` : ''}`);
|
||||
// Import routines if present
|
||||
const routineResult = importRoutines(data, routines);
|
||||
if (data.routines && data.routines.length > 0) {
|
||||
const existingRoutineIds = new Set(routines.map((r) => r.id));
|
||||
const newRoutines = data.routines.filter((r) => !existingRoutineIds.has(r.id));
|
||||
if (newRoutines.length > 0) {
|
||||
useRoutineStore.setState((s) => ({ routines: [...s.routines, ...newRoutines] }));
|
||||
}
|
||||
}
|
||||
const parts = [`Imported ${timerResult.imported} timers`];
|
||||
if (routineResult.imported > 0) parts.push(`${routineResult.imported} routines`);
|
||||
const totalSkipped = timerResult.skipped + routineResult.skipped;
|
||||
if (totalSkipped > 0) parts.push(`${totalSkipped} skipped`);
|
||||
const allErrors = [...timerResult.errors, ...routineResult.errors];
|
||||
setImportStatus(`${parts.join(', ')}.${allErrors.length > 0 ? ` Errors: ${allErrors.join(', ')}` : ''}`);
|
||||
} catch {
|
||||
setImportStatus('Failed to read file.');
|
||||
}
|
||||
@ -300,15 +316,15 @@ export default function HistoryPage() {
|
||||
style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)' }}
|
||||
>
|
||||
<h3 className="text-sm font-semibold mb-2 flex items-center gap-2" style={{ color: 'var(--cm-text-primary)' }}>
|
||||
<Download size={16} /> Export Timers
|
||||
<Download size={16} /> Export Data
|
||||
</h3>
|
||||
<p className="text-xs mb-3" style={{ color: 'var(--cm-text-tertiary)' }}>
|
||||
Download all {timers.length} timers as a JSON file.
|
||||
Download all {timers.length} timers and {routines.length} routines as a JSON file.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleExport}
|
||||
disabled={timers.length === 0}
|
||||
disabled={timers.length === 0 && routines.length === 0}
|
||||
className="flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium cursor-pointer disabled:opacity-30"
|
||||
style={{ backgroundColor: 'var(--cm-accent)', color: '#fff' }}
|
||||
>
|
||||
@ -331,10 +347,10 @@ export default function HistoryPage() {
|
||||
style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)' }}
|
||||
>
|
||||
<h3 className="text-sm font-semibold mb-2 flex items-center gap-2" style={{ color: 'var(--cm-text-primary)' }}>
|
||||
<Upload size={16} /> Import Timers (JSON)
|
||||
<Upload size={16} /> Import Data (JSON)
|
||||
</h3>
|
||||
<p className="text-xs mb-3" style={{ color: 'var(--cm-text-tertiary)' }}>
|
||||
Restore timers from a previously exported ChronoMind JSON file.
|
||||
Restore timers and routines from a previously exported ChronoMind JSON file.
|
||||
</p>
|
||||
<label
|
||||
className="inline-block px-4 py-2 rounded-lg text-sm font-medium cursor-pointer"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user