Add gender retio to stats

This commit is contained in:
MartinBraquet
2026-03-08 16:55:58 +01:00
parent 0ea9ee969e
commit 74f948e6ca
3 changed files with 70 additions and 8 deletions

View File

@@ -1,22 +1,64 @@
import {getMessagesCount} from 'api/get-messages-count'
import {HOUR_MS} from 'common/util/time'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {APIHandler} from './helpers/endpoint'
// Server-side cache for stats data
let cachedData: any = null
let cacheTimestamp: number = 0
const CACHE_DURATION_MS = HOUR_MS
export const stats: APIHandler<'stats'> = async (_, _auth) => {
const now = Date.now()
// Return cached data if still valid
if (cachedData && now - cacheTimestamp < CACHE_DURATION_MS) {
console.log('cached stats')
console.log(cachedData)
return cachedData
}
const pg = createSupabaseDirectClient()
const [userCount, profileCount, eventsCount, messagesCount] = await Promise.all([
const [userCount, profileCount, eventsCount, messagesCount, genderStats] = await Promise.all([
pg.one(`SELECT COUNT(*)::int as count FROM users`),
pg.one(`SELECT COUNT(*)::int as count FROM profiles`),
pg.one(`SELECT COUNT(*)::int as count FROM events WHERE event_start_time > now()`),
getMessagesCount(),
pg.manyOrNone(
`SELECT gender, COUNT(*)::int as count FROM profiles WHERE gender IS NOT NULL GROUP BY gender`,
),
])
return {
// Calculate gender ratios
const genderRatio: Record<string, number> = {}
let totalWithGender = 0
genderStats?.forEach((stat: any) => {
if (!['male', 'female'].includes(stat.gender)) return
genderRatio[stat.gender] = stat.count
totalWithGender += stat.count
})
// Convert to percentages
const genderPercentage: Record<string, number> = {}
Object.entries(genderRatio).forEach(([gender, count]) => {
genderPercentage[gender] = Math.round((count / totalWithGender) * 100)
})
const result = {
users: userCount.count,
profiles: profileCount.count,
upcomingEvents: eventsCount.count,
messages: messagesCount.count,
genderRatio: genderPercentage,
genderCounts: genderRatio,
}
// Update cache
cachedData = result
cacheTimestamp = now
return result
}

View File

@@ -154,6 +154,9 @@ export const API = (_apiTypeCheck = {
users: number
profiles: number
upcomingEvents: number
messages: number
genderRatio: Record<string, number>
genderCounts: Record<string, number>
},
summary: 'Get platform statistics',
tag: 'General',

View File

@@ -5,12 +5,14 @@ import {PageBase} from 'web/components/page-base'
import {SEO} from 'web/components/SEO'
import ChartMembers from 'web/components/widgets/charts'
import StatBox from 'web/components/widgets/stat-box'
import {api} from 'web/lib/api'
import {useT} from 'web/lib/locale'
import {getCount} from 'web/lib/supabase/users'
export default function Stats() {
const t = useT()
const [data, setData] = useState<Record<string, number | null>>({})
const [statsData, setStatsData] = useState<any>(null)
useEffect(() => {
async function load() {
@@ -27,14 +29,23 @@ export default function Stats() {
'vote_results',
] as const
const settled = await Promise.allSettled(tables.map((t) => getCount(t)))
const [settled, statsResult] = await Promise.allSettled([
Promise.allSettled(tables.map((t) => getCount(t))),
api('stats', {}),
])
const result: Record<string, number | null> = {}
settled.forEach((res, i) => {
const key = tables[i]
if (res.status === 'fulfilled') result[key] = res.value
else result[key] = null
})
if (settled.status === 'fulfilled') {
settled.value.forEach((res, i) => {
const key = tables[i]
if (res.status === 'fulfilled') result[key] = res.value
else result[key] = null
})
}
if (statsResult.status === 'fulfilled') {
setStatsData(statsResult.value)
}
setData(result)
}
@@ -108,6 +119,12 @@ export default function Stats() {
label={t('stats.endorsements', 'Endorsements')}
/>
)}
{!!statsData?.genderRatio && (
<StatBox
value={`${statsData.genderRatio.male} / ${statsData.genderRatio.female}`}
label={t('stats.gender_ratio', 'Male / Female Ratio')}
/>
)}
</Col>
</Col>
</PageBase>