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' && (
}
onClick={() => {
setIsRedeemModalOpen(true)
}}
sx={{ mt: 'auto' }}
>
{t('redeem')}
)}
{/* 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