'use client';
import { useEffect, useRef, useState, Suspense } from 'react';
import { Plus, FileBarChart, Calendar, Target, TrendingUp, Clock, Trash2 } from 'lucide-react';
import dynamic from 'next/dynamic';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
// Lazy load TipTap report editor (~80KB)
const ReportEditor = dynamic(
() => import('@/components/reports/report-editor').then((m) => m.ReportEditor),
{
ssr: false,
loading: () => (
),
}
);
// Lazy load report templates
const ReportTemplates = dynamic(
() => import('@/components/reports/report-templates').then((m) => m.ReportTemplates),
{
ssr: false,
loading: () => (
),
}
);
interface Report {
id: string;
title: string;
content: string;
report_type: 'weekly' | 'monthly' | 'project' | 'habit' | 'custom';
date_range_start?: string;
date_range_end?: string;
domain: string;
created: string;
updated: string;
}
export default function ReportsPage() {
const [reports, setReports] = useState([]);
const [selectedReport, setSelectedReport] = useState(null);
const [loading, setLoading] = useState(true);
const [showTemplates, setShowTemplates] = useState(false);
const [error, setError] = useState(null);
const [saveStatus, setSaveStatus] = useState<'Saved' | 'Saving' | 'Failed'>('Saved');
const [reportToDelete, setReportToDelete] = useState(null);
const [deleting, setDeleting] = useState(false);
const pendingSave = useRef<{ id: string; updates: Partial } | null>(null);
const saveTimer = useRef | null>(null);
const saveVersion = useRef(0);
useEffect(() => {
fetchReports();
return () => {
if (saveTimer.current) clearTimeout(saveTimer.current);
};
}, []);
async function fetchReports() {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/reports?sort=-created');
if (!response.ok) throw new Error('Unable to load reports.');
const data = await response.json();
const reportsList = data.items || [];
setReports(reportsList);
setSelectedReport((current) => current || reportsList[0] || null);
} catch (error) {
console.error('Failed to fetch reports:', error);
setError('Unable to load reports. Please try again.');
} finally {
setLoading(false);
}
}
async function createReport(overrides?: Partial) {
try {
const response = await fetch('/api/reports', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Untitled report',
content: '',
report_type: 'custom',
domain: 'personal',
...overrides,
}),
});
if (!response.ok) throw new Error('Unable to create report.');
const newReport = await response.json();
setReports((current) => [newReport, ...current]);
setSelectedReport(newReport);
setShowTemplates(false);
toast.success('Report created');
} catch (error) {
console.error('Failed to create report:', error);
toast.error('Unable to create report');
}
}
function scheduleSave(reportId: string, updates: Partial) {
const version = ++saveVersion.current;
pendingSave.current = {
id: reportId,
updates: { ...(pendingSave.current?.id === reportId ? pendingSave.current.updates : {}), ...updates },
};
if (saveTimer.current) clearTimeout(saveTimer.current);
setSaveStatus('Saving');
saveTimer.current = setTimeout(() => {
const save = pendingSave.current;
pendingSave.current = null;
if (save) updateReport(save.id, save.updates, version);
}, 700);
}
async function updateReport(reportId: string, updates: Partial, version: number) {
try {
const response = await fetch(`/api/reports/${reportId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!response.ok) throw new Error('Unable to save report.');
const updated = await response.json();
setReports((current) => current.map((report) => (report.id === reportId ? updated : report)));
if (saveVersion.current === version) setSaveStatus('Saved');
} catch (error) {
console.error('Failed to update report:', error);
if (saveVersion.current === version) setSaveStatus('Failed');
toast.error('Unable to save report');
}
}
async function deleteReport() {
if (!reportToDelete) return;
if (pendingSave.current?.id === reportToDelete.id && saveTimer.current) {
clearTimeout(saveTimer.current);
pendingSave.current = null;
}
++saveVersion.current;
setDeleting(true);
try {
const response = await fetch(`/api/reports/${reportToDelete.id}`, { method: 'DELETE' });
if (!response.ok) throw new Error('Unable to delete report.');
setReports((current) => current.filter((report) => report.id !== reportToDelete.id));
setSelectedReport((selected) =>
selected?.id === reportToDelete.id ? reports.find((report) => report.id !== reportToDelete.id) || null : selected
);
setReportToDelete(null);
toast.success('Report deleted');
} catch (error) {
console.error('Failed to delete report:', error);
toast.error('Unable to delete report');
} finally {
setDeleting(false);
}
}
function getReportTypeIcon(type: string) {
switch (type) {
case 'weekly':
return ;
case 'monthly':
return ;
case 'project':
return ;
case 'habit':
return ;
case 'custom':
return ;
default:
return ;
}
}
if (loading) {
return Loading reports...
;
}
if (error) {
return ;
}
if (showTemplates) {
return (
Loading templates...
}
>
{
createReport({
title: template.name,
report_type: template.type,
content: template.content,
});
}}
onCancel={() => setShowTemplates(false)}
/>
);
}
return (
Reports
Step back and see what changed.
{/* Reports list */}
{reports.length === 0 ? (
No reports yet
) : (
{reports.map((report) => (
))}
)}
{/* Report editor */}
{selectedReport ? (
{selectedReport.report_type}
{selectedReport.domain}
{selectedReport.date_range_start && selectedReport.date_range_end && (
{new Date(selectedReport.date_range_start).toLocaleDateString()} -{' '}
{new Date(selectedReport.date_range_end).toLocaleDateString()}
)}
}
>
{ setSelectedReport({ ...selectedReport, content }); scheduleSave(selectedReport.id, { content }); }}
/>
) : (
Select a report or create a new one
)}
);
}