import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis, } from 'recharts' import { AccountBalanceWallet, Analytics, AssignmentTurnedIn, CreditCard, EmojiEvents, MilitaryTech, Redeem, Star, SwapHoriz, Timeline, Toll, TrendingUp, WorkspacePremium, } from '@mui/icons-material' import { Avatar, Box, Button, Card, CardContent, Chip, Container, Grid, Option, Select, Stack, Tab, TabList, Tabs, Typography, } from '@mui/joy' import { useEffect, useState } from 'react' import moment from 'moment' import { useTranslation } from 'react-i18next' import LoadingComponent from '../components/Loading.jsx' import { useChoresHistory } from '../../queries/ChoreQueries.jsx' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx' import { RedeemPoints } from '../../utils/Fetcher.jsx' import { resolvePhotoURL } from '../../utils/Helpers.jsx' import RedeemPointsModal from '../Modals/RedeemPointsModal' const UserPoints = () => { const { t } = useTranslation('points') const [tabValue, setTabValue] = useState(7) const [isRedeemModalOpen, setIsRedeemModalOpen] = useState(false) const [leaderboardMode, setLeaderboardMode] = useState('points') // 'points' or 'tasks' const { data: circleMembersData, isLoading: isCircleMembersLoading, handleRefetch: handleCircleMembersRefetch, } = useCircleMembers() const { data: choresHistoryData, isLoading: isChoresHistoryLoading, handleLimitChange: handleChoresHistoryLimitChange, } = useChoresHistory(7, true) const { data: userProfile } = useUserProfile() const [selectedUser, setSelectedUser] = useState(userProfile?.id) const [circleUsers, setCircleUsers] = useState([]) const [selectedHistory, setSelectedHistory] = useState([]) useEffect(() => { if (circleMembersData && choresHistoryData && userProfile) { setCircleUsers(circleMembersData.res) setSelectedHistory( generateWeeklySummary(choresHistoryData, userProfile?.id), ) } }, [circleMembersData, choresHistoryData, userProfile]) useEffect(() => { if (choresHistoryData) { var history if (tabValue === 7) { history = generateWeeklySummary(choresHistoryData, selectedUser) } else if (tabValue === 30) { history = generateMonthSummary(choresHistoryData, selectedUser) } else if (tabValue === 6 * 30) { history = generateMonthlySummary(choresHistoryData, selectedUser) } else { history = generateYearlySummary(choresHistoryData, selectedUser) } setSelectedHistory(history) } }, [selectedUser, choresHistoryData, tabValue]) useEffect(() => { setSelectedUser(userProfile?.id) }, [userProfile]) const generateWeeklySummary = (history, userId) => { const daysAggregated = [] for (let i = 6; i > -1; i--) { const currentDate = new Date() currentDate.setDate(currentDate.getDate() - i) daysAggregated.push({ label: moment(currentDate).format('ddd'), points: 0, tasks: 0, }) } history.forEach(chore => { const dayName = moment(chore.performedAt).format('ddd') const dayIndex = daysAggregated.findIndex(dayData => { if (userId) return dayData.label === dayName && chore.completedBy === userId return dayData.label === dayName }) if (dayIndex !== -1) { if (chore.points) daysAggregated[dayIndex].points += chore.points daysAggregated[dayIndex].tasks += 1 } }) return daysAggregated } const generateMonthSummary = (history, userId) => { const daysAggregated = [] for (let i = 29; i > -1; i--) { const currentDate = new Date() currentDate.setDate(currentDate.getDate() - i) daysAggregated.push({ label: moment(currentDate).format('D'), points: 0, tasks: 0, }) } history.forEach(chore => { const dayName = moment(chore.performedAt).format('D') const dayIndex = daysAggregated.findIndex(dayData => { if (userId) return dayData.label === dayName && chore.completedBy === userId return dayData.label === dayName }) if (dayIndex !== -1) { if (chore.points) daysAggregated[dayIndex].points += chore.points daysAggregated[dayIndex].tasks += 1 } }) return daysAggregated } const generateMonthlySummary = (history, userId) => { const monthlyAggregated = [] for (let i = 5; i > -1; i--) { const currentMonth = new Date() currentMonth.setMonth(currentMonth.getMonth() - i) monthlyAggregated.push({ label: moment(currentMonth).format('MMM'), points: 0, tasks: 0, }) } history.forEach(chore => { const monthName = moment(chore.performedAt).format('MMM') const monthIndex = monthlyAggregated.findIndex(monthData => { if (userId) return monthData.label === monthName && chore.completedBy === userId return monthData.label === monthName }) if (monthIndex !== -1) { if (chore.points) monthlyAggregated[monthIndex].points += chore.points monthlyAggregated[monthIndex].tasks += 1 } }) return monthlyAggregated } const generateYearlySummary = (history, userId) => { const yearlyAggregated = [] for (let i = 11; i > -1; i--) { const currentYear = new Date() currentYear.setFullYear(currentYear.getFullYear() - i) yearlyAggregated.push({ label: moment(currentYear).format('YYYY'), points: 0, tasks: 0, }) } history.forEach(chore => { const yearName = moment(chore.performedAt).format('YYYY') const yearIndex = yearlyAggregated.findIndex(yearData => { if (userId) return yearData.label === yearName && chore.completedBy === userId return yearData.label === yearName }) if (yearIndex !== -1) { if (chore.points) yearlyAggregated[yearIndex].points += chore.points yearlyAggregated[yearIndex].tasks += 1 } }) return yearlyAggregated } if (isChoresHistoryLoading || isCircleMembersLoading || !userProfile) { return } // Calculate leaderboard data for the current time period const calculateLeaderboard = () => { if (!choresHistoryData || !circleUsers.length) return [] // Calculate points for each user in the current time period const userPeriodStats = {} // Initialize stats for all users circleUsers.forEach(user => { userPeriodStats[user.userId] = { userId: user.userId, displayName: user.displayName, image: user.image, totalPoints: user.points || 0, availablePoints: (user.points || 0) - (user.pointsRedeemed || 0), periodPoints: 0, periodTasks: 0, } }) // Calculate period-specific stats from history choresHistoryData.forEach(historyEntry => { const userId = historyEntry.completedBy if (userPeriodStats[userId]) { userPeriodStats[userId].periodPoints += historyEntry.points || 0 userPeriodStats[userId].periodTasks += 1 } }) // Convert to array and sort by selected mode const sortField = leaderboardMode === 'points' ? 'periodPoints' : 'periodTasks' return Object.values(userPeriodStats) .sort((a, b) => b[sortField] - a[sortField]) .map((user, index) => ({ ...user, rank: index + 1, avgPointsPerTask: user.periodTasks > 0 ? (user.periodPoints / user.periodTasks).toFixed(1) : 0, })) } const leaderboardData = calculateLeaderboard() // Get trophy icons for top 3 const getTrophyIcon = rank => { switch (rank) { case 1: return // Gold case 2: return ( ) // Silver case 3: return // Bronze default: return } } return ( {/* Enhanced Leaderboard Header Section */} {/* Title Row */} {leaderboardMode === 'points' ? t('leaderboard.titlePoints') : t('leaderboard.titleTasks')} {leaderboardMode === 'points' ? t('leaderboard.subtitlePoints') : t('leaderboard.subtitleTasks')} {/* Filters Row - Responsive */} {/* Time Period Filter */} { setTabValue(tabValue) handleChoresHistoryLimitChange(tabValue) }} value={tabValue} size='sm' sx={{ borderRadius: 6, backgroundColor: 'background.surface', border: '1px solid', borderColor: 'divider', }} > {[ { label: t('tabs.short7d'), value: 7 }, { label: t('tabs.short6m'), value: 6 * 30 }, { label: t('tabs.shortAll'), value: 24 * 30 }, ].map((tab, index) => ( {tab.label} ))} {/* Toggle between points and tasks */} setLeaderboardMode('points')} > {t('leaderboard.modePoints')} setLeaderboardMode('tasks')} > {t('leaderboard.modeTasks')} {/* Leaderboard Cards */} {leaderboardData.map((user, index) => ( { if (user.userId !== selectedUser) { setSelectedUser(user.userId) setSelectedHistory( generateWeeklySummary(choresHistoryData, user.userId), ) } }} > {/* Rank Badge */} {getTrophyIcon(user.rank)} #{user.rank} {/* User Avatar and Info */} {user.displayName?.charAt(0)} {user.displayName} {user.userId === userProfile.id && ( {t('leaderboard.you')} )} {t('leaderboard.tasksAndAvg', { tasks: user.periodTasks, avg: user.avgPointsPerTask, })} {/* Metric Display */} {leaderboardMode === 'points' ? ( ) : ( )} {leaderboardMode === 'points' ? user.periodPoints : user.periodTasks} {leaderboardMode === 'points' ? t('leaderboard.available', { count: user.availablePoints, }) : t('leaderboard.points', { count: user.periodPoints, })} {/* Selection Indicator */} {user.userId === selectedUser && ( )} {index < leaderboardData.length - 1 && ( )} ))} {/* Filters Section Header */} {t('filter.analysisTitle')} {/* Improved Filter Bar */} {t('filter.title')} {/* User Filter */} {t('filter.showFor')} {/* Time Period Filter */} {t('filter.timePeriod')} { setTabValue(tabValue) handleChoresHistoryLimitChange(tabValue) }} value={tabValue} sx={{ borderRadius: 8, backgroundColor: 'background.surface', border: '1px solid', borderColor: 'divider', }} > {[ { label: t('tabs.days7'), value: 7 }, { label: t('tabs.months6'), value: 6 * 30 }, { label: t('tabs.allTime'), value: 24 * 30 }, ].map((tab, index) => ( {tab.label} ))} {/* Redeem Points Button */} {circleUsers.find(user => user.userId === userProfile.id)?.role === 'admin' && ( )} {/* Points Status Cards */} {(() => { const selectedUserData = circleUsers.find( user => user.userId === selectedUser, ) const totalPoints = selectedUserData?.points || 0 const redeemedPoints = selectedUserData?.pointsRedeemed || 0 const availablePoints = totalPoints - redeemedPoints const periodStats = leaderboardData.find( user => user.userId === selectedUser, ) const periodPoints = selectedHistory.reduce( (sum, item) => sum + (item.points || 0), 0, ) const pointsCards = [ { icon: , title: t('cards.available.title'), text: t('leaderboard.points', { count: availablePoints }), subtext: t('cards.available.subtext'), }, { icon: , title: t('cards.redeemed.title'), text: t('leaderboard.points', { count: redeemedPoints }), subtext: t('cards.redeemed.subtext'), }, { icon: , title: t('cards.total.title'), text: t('leaderboard.points', { count: totalPoints }), subtext: t('cards.total.subtext'), }, { icon: , title: t('cards.period.title'), text: t('leaderboard.points', { count: periodPoints }), subtext: tabValue === 24 * 30 ? t('cards.period.allTime') : tabValue === 6 * 30 ? t('cards.period.last6Months') : t('cards.period.lastDays', { count: tabValue }), }, ] return pointsCards.map((card, index) => ( {card.icon} {card.title} {card.text} {card.subtext} )) })()} {/* Current Filter Summary */} {t('summary.showingFor')}{' '} {circleUsers.find(user => user.userId === selectedUser) ?.displayName || t('summary.unknownUser')} {' '} {t('summary.overThe')}{' '} {tabValue === 24 * 30 ? t('summary.allTime') : tabValue === 6 * 30 ? t('summary.last6Months') : t('summary.lastDays', { count: tabValue })} {/* Chart Section Header */} {t('trend.title')} {/* Bar Chart for points overtime */} { const user = circleUsers.find(u => u.userId === selectedUser) const availablePoints = user ? (user.points || 0) - (user.pointsRedeemed || 0) : 0 return { onClose: () => { setIsRedeemModalOpen(false) }, isOpen: isRedeemModalOpen, available: availablePoints, user: user, onSave: ({ userId, points }) => { RedeemPoints(userId, points, userProfile.circleID) .then(() => { setIsRedeemModalOpen(false) handleCircleMembersRefetch() }) .catch(err => { console.log(err) }) }, } })()} /> ) } export default UserPoints