diff --git a/frontend/src/pages/Reports.tsx b/frontend/src/pages/Reports.tsx new file mode 100644 index 0000000..3d4f534 --- /dev/null +++ b/frontend/src/pages/Reports.tsx @@ -0,0 +1,400 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; +import { choreLogsService, WeeklyChoreReport } from '../api/choreLogs'; +import { + ChartBarIcon, + TrophyIcon, + CalendarIcon, + UserGroupIcon, + ArrowLeftIcon, + ChevronLeftIcon, + ChevronRightIcon +} from '@heroicons/react/24/outline'; + +const Reports: React.FC = () => { + const { user, logout } = useAuth(); + const [report, setReport] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [weeksAgo, setWeeksAgo] = useState(0); + const [selectedUserId, setSelectedUserId] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + loadReport(); + }, [weeksAgo, selectedUserId]); + + const loadReport = async () => { + setIsLoading(true); + setError(null); + try { + const data = await choreLogsService.getWeeklyReport( + selectedUserId || undefined, + weeksAgo + ); + setReport(data); + } catch (error) { + console.error('Failed to load report:', error); + setError('Failed to load report. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + }; + + const getWeekLabel = () => { + if (weeksAgo === 0) return 'This Week'; + if (weeksAgo === 1) return 'Last Week'; + return `${weeksAgo} Weeks Ago`; + }; + + if (isLoading) { + return ( +
+
+
+
+
+

Loading report...

+
+
+
+
+ ); + } + + if (error) { + return ( +
+
+
+

{error}

+ +
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+ + + +
+

+ Weekly Reports +

+

+ Family chore completion statistics +

+
+
+
+ + {user?.full_name || user?.username} + + +
+
+
+
+ +
+ {/* Week Navigation */} +
+
+ + +
+
+ + {getWeekLabel()} +
+ {report && ( +

+ {formatDate(report.start_date)} - {formatDate(report.end_date)} +

+ )} +
+ + +
+
+ + {report && ( + <> + {/* Stats Cards */} +
+ {/* Total Completions */} +
+
+
+ +
+
+

+ Total Completions +

+

+ {report.total_completions} +

+
+
+
+ + {/* Active Users */} +
+
+
+ +
+
+

+ Active Members +

+

+ {Object.keys(report.completions_by_user).length} +

+
+
+
+ + {/* Different Chores */} +
+
+
+ +
+
+

+ Different Chores +

+

+ {Object.keys(report.completions_by_chore).length} +

+
+
+
+
+ + {/* Top Performers */} +
+

+ + Top Performers +

+
+ {report.top_performers.map((performer, index) => ( +
+
+
+ {index + 1} +
+ {performer.avatar_url ? ( + {performer.username} + ) : ( +
+ {performer.username.charAt(0).toUpperCase()} +
+ )} +
+

+ {performer.username} +

+

+ {performer.count} {performer.count === 1 ? 'chore' : 'chores'} completed +

+
+
+
+
+ {performer.count} +
+
+
+ ))} + {report.top_performers.length === 0 && ( +

+ No completions recorded this week +

+ )} +
+
+ + {/* Completions by Day */} +
+

+ Completions by Day +

+
+ {['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].map((day) => { + const count = report.completions_by_day[day] || 0; + const maxCount = Math.max(...Object.values(report.completions_by_day)); + const percentage = maxCount > 0 ? (count / maxCount) * 100 : 0; + + return ( +
+
+ {day} +
+
+
+
+ {count > 0 && ( + + {count} + + )} +
+
+
+
+ ); + })} +
+
+ + {/* Completions by Chore */} +
+

+ Completions by Chore +

+
+ {Object.entries(report.completions_by_chore) + .sort(([, a], [, b]) => b - a) + .map(([chore, count]) => ( +
+ {chore} + {count} +
+ ))} + {Object.keys(report.completions_by_chore).length === 0 && ( +

+ No chores completed this week +

+ )} +
+
+ + {/* Recent Completions */} +
+

+ Recent Completions +

+
+ {report.recent_completions.map((completion) => ( +
+
+ {completion.user_avatar ? ( + {completion.user_name + ) : ( +
+ {completion.user_name?.charAt(0).toUpperCase()} +
+ )} +
+

+ {completion.chore_title} +

+

+ by {completion.user_name} + {completion.notes && ( + - "{completion.notes}" + )} +

+
+
+
+

+ {new Date(completion.completed_at).toLocaleDateString()} +

+

+ {new Date(completion.completed_at).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + })} +

+
+
+ ))} + {report.recent_completions.length === 0 && ( +

+ No completions to show +

+ )} +
+
+ + )} +
+
+ ); +}; + +export default Reports;