Add formatContribution helper in app/config/contributions.js and route all member-facing and admin contribution displays through cadence-aware expressions so annual members see /yr instead of /mo. Normalize annual amounts to monthly equivalents in the admin dashboard revenue aggregate now that contributionAmount is stored in cadence units.
62 lines
1.8 KiB
JavaScript
62 lines
1.8 KiB
JavaScript
import Member from '../../models/member.js'
|
|
import Event from '../../models/event.js'
|
|
import { connectDB } from '../../utils/mongoose.js'
|
|
import { requireAdmin } from '../../utils/auth.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
await requireAdmin(event)
|
|
await connectDB()
|
|
|
|
// Get stats
|
|
const totalMembers = await Member.countDocuments()
|
|
const now = new Date()
|
|
const activeEvents = await Event.countDocuments({
|
|
startDate: { $lte: now },
|
|
endDate: { $gte: now }
|
|
})
|
|
|
|
// Calculate monthly revenue from member contributions.
|
|
// contributionAmount is stored in cadence units (monthly = $/mo, annual = $/yr),
|
|
// so normalize annual amounts to monthly equivalents before summing.
|
|
const members = await Member.find({}, 'contributionAmount billingCadence').lean()
|
|
const monthlyRevenue = members.reduce((total, member) => {
|
|
const amt = member.contributionAmount || 0
|
|
const monthlyEquivalent = member.billingCadence === 'annual' ? amt / 12 : amt
|
|
return total + monthlyEquivalent
|
|
}, 0)
|
|
|
|
const pendingSlackInvites = await Member.countDocuments({ slackInvited: false })
|
|
|
|
// Get recent members (last 5)
|
|
const recentMembers = await Member.find()
|
|
.sort({ createdAt: -1 })
|
|
.limit(5)
|
|
.lean()
|
|
|
|
// Get upcoming events (next 5)
|
|
const upcomingEvents = await Event.find({
|
|
startDate: { $gte: now }
|
|
})
|
|
.sort({ startDate: 1 })
|
|
.limit(5)
|
|
.lean()
|
|
|
|
return {
|
|
stats: {
|
|
totalMembers,
|
|
activeEvents,
|
|
monthlyRevenue,
|
|
pendingSlackInvites
|
|
},
|
|
recentMembers,
|
|
upcomingEvents
|
|
}
|
|
} catch (error) {
|
|
if (error.statusCode) throw error
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to fetch dashboard data'
|
|
})
|
|
}
|
|
})
|