mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-26 02:21:06 -04:00
* Test * Add pretty formatting * Fix Tests * Fix Tests * Fix Tests * Fix * Add pretty formatting fix * Fix * Test * Fix tests * Clean typeckech * Add prettier check * Fix api tsconfig * Fix api tsconfig * Fix tsconfig * Fix * Fix * Prettier
174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
import {useEffect, useState} from 'react'
|
|
import {Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'
|
|
import {useT} from 'web/lib/locale'
|
|
import {getCompletedProfilesCreations, getProfilesCreations} from 'web/lib/supabase/users'
|
|
|
|
// Helper to convert rows into date -> count map
|
|
function buildCounts(rows: any[]) {
|
|
const counts: Record<string, number> = {}
|
|
for (const r of rows) {
|
|
const date = new Date(r.created_time).toISOString().split('T')[0]
|
|
counts[date] = (counts[date] || 0) + 1
|
|
}
|
|
return counts
|
|
}
|
|
|
|
// Helper to turn count map into cumulative by sorted date array
|
|
function cumulativeFromCounts(counts: Record<string, number>, sortedDates: string[]) {
|
|
const out: Record<string, number> = {}
|
|
let prev = 0
|
|
for (const d of sortedDates) {
|
|
const v = counts[d] || 0
|
|
prev += v
|
|
out[d] = prev
|
|
}
|
|
return out
|
|
}
|
|
|
|
export default function ChartMembers() {
|
|
const [data, setData] = useState<any[]>([])
|
|
|
|
const [chartHeight, setChartHeight] = useState<number>(400)
|
|
const t = useT()
|
|
|
|
useEffect(() => {
|
|
// Set responsive chart height: 300px on small widths, 400px otherwise
|
|
function applyHeight() {
|
|
if (typeof window !== 'undefined') {
|
|
const isSmall = window.innerWidth < 420
|
|
setChartHeight(isSmall ? 320 : 400)
|
|
}
|
|
}
|
|
|
|
applyHeight()
|
|
window.addEventListener('resize', applyHeight)
|
|
return () => window.removeEventListener('resize', applyHeight)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
const [allProfiles, complatedProfiles] = await Promise.all([
|
|
getProfilesCreations(),
|
|
getCompletedProfilesCreations(),
|
|
])
|
|
|
|
const countsAll = buildCounts(allProfiles)
|
|
const countsCompleted = buildCounts(complatedProfiles)
|
|
|
|
// Build a full daily date range from min to max date for equidistant time axis
|
|
const allDates = Object.keys(countsAll)
|
|
const completedDates = Object.keys(countsCompleted)
|
|
const minDateStr = [...allDates, ...completedDates].sort((a, b) => a.localeCompare(b))[0]
|
|
const maxDateStr = [...allDates, ...completedDates].sort((a, b) => b.localeCompare(a))[0]
|
|
|
|
function toISODate(d: Date) {
|
|
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))
|
|
.toISOString()
|
|
.split('T')[0]
|
|
}
|
|
|
|
function addDays(d: Date, days: number) {
|
|
const nd = new Date(d)
|
|
nd.setUTCDate(nd.getUTCDate() + days)
|
|
return nd
|
|
}
|
|
|
|
function buildDailyRange(startStr: string, endStr: string) {
|
|
const out: string[] = []
|
|
const start = new Date(startStr + 'T00:00:00.000Z')
|
|
const end = new Date(endStr + 'T00:00:00.000Z')
|
|
for (let d = start; d <= end; d = addDays(d, 1)) {
|
|
out.push(toISODate(d))
|
|
}
|
|
return out
|
|
}
|
|
|
|
const dates = buildDailyRange(minDateStr, maxDateStr)
|
|
|
|
const cumAll = cumulativeFromCounts(countsAll, dates)
|
|
const cumCompleted = cumulativeFromCounts(countsCompleted, dates)
|
|
|
|
const merged = dates.map((date) => ({
|
|
date,
|
|
dateTs: new Date(date + 'T00:00:00.000Z').getTime(),
|
|
profilesCreations: cumAll[date] || 0,
|
|
profilesCompletedCreations: cumCompleted[date] || 0,
|
|
}))
|
|
|
|
setData(merged)
|
|
}
|
|
|
|
void load()
|
|
}, [])
|
|
|
|
// One LineChart with two Line series sharing the same data array
|
|
return (
|
|
<div>
|
|
<ResponsiveContainer width="100%" height={chartHeight}>
|
|
<LineChart data={data} margin={{top: 24, right: 16, bottom: 24, left: -20}}>
|
|
<text
|
|
x="50%"
|
|
y="24"
|
|
textAnchor="middle"
|
|
dominantBaseline="middle"
|
|
style={{
|
|
fontSize: '16px',
|
|
fontWeight: 600,
|
|
fill: 'rgb(var(--color-primary-900))',
|
|
}}
|
|
>
|
|
{t('stats.number_members', 'Number of Members')}
|
|
</text>
|
|
{/*<CartesianGrid strokeDasharray="3 3"/>*/}
|
|
<XAxis
|
|
dataKey="dateTs"
|
|
type="number"
|
|
scale="time"
|
|
domain={['dataMin', 'dataMax']}
|
|
tickFormatter={(ts) => new Date(ts).toISOString().split('T')[0]}
|
|
label={{
|
|
value: t('charts.date', 'Date'),
|
|
position: 'insideBottomRight',
|
|
offset: -5,
|
|
}}
|
|
/>
|
|
<YAxis />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'rgb(var(--color-canvas-100))',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
color: 'rgb(var(--color-primary-900))',
|
|
}}
|
|
labelStyle={{
|
|
color: 'rgb(var(--color-primary-900))',
|
|
}}
|
|
labelFormatter={(value, payload) =>
|
|
(payload && payload[0] && payload[0].payload?.date) ||
|
|
new Date(value as number).toISOString().split('T')[0]
|
|
}
|
|
/>
|
|
<Legend />
|
|
<Line
|
|
type="monotone"
|
|
dataKey="profilesCreations"
|
|
name={t('stats.total', 'Total')}
|
|
stroke="rgb(var(--color-primary-900))"
|
|
strokeWidth={2}
|
|
dot={false}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="profilesCompletedCreations"
|
|
name={t('stats.with_bio', 'Completed')}
|
|
stroke="#9ca3af"
|
|
strokeDasharray="4 2"
|
|
strokeWidth={2}
|
|
dot={false}
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
)
|
|
}
|