Skip to content

Commit 559a8bb

Browse files
shebz2023JacquelineTuyisenge
authored andcommitted
user Growth overtime chart
1 parent 548376c commit 559a8bb

File tree

4 files changed

+282
-131
lines changed

4 files changed

+282
-131
lines changed

src/Chart/LineChart.tsx

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import React, { useEffect, useState } from 'react';
2+
import {
3+
CartesianGrid,
4+
Legend,
5+
Line,
6+
LineChart,
7+
ResponsiveContainer,
8+
Tooltip,
9+
XAxis,
10+
YAxis,
11+
} from 'recharts';
12+
import { useLazyQuery, gql } from '@apollo/client';
13+
import { UserInterface } from '../pages/TraineeAttendanceTracker';
14+
import { Organization } from '../components/Organizations';
15+
16+
export function UserChart() {
17+
const GET_ALL_ORG_USERS = gql`
18+
query GetAllOrgUsers {
19+
getAllOrgUsers {
20+
totalUsers
21+
organizations {
22+
organization {
23+
id
24+
name
25+
description
26+
admin {
27+
id
28+
email
29+
profile {
30+
name
31+
phoneNumber
32+
}
33+
}
34+
status
35+
}
36+
members {
37+
email
38+
profile {
39+
name
40+
}
41+
}
42+
monthPercentage
43+
loginsCount
44+
recentLocation
45+
}
46+
}
47+
}
48+
`;
49+
50+
const GET_REGISTRATION_STATS = gql`
51+
query GetRegistrationStats {
52+
getRegistrationStats {
53+
year
54+
stats {
55+
month
56+
users
57+
organizations
58+
}
59+
}
60+
}
61+
`;
62+
63+
interface AllOrgUsersInterface {
64+
totalUsers: number;
65+
organizations: {
66+
organization: Organization;
67+
members: UserInterface[];
68+
loginsCount: number;
69+
monthPercentage: number;
70+
recentLocation: string | null;
71+
}[];
72+
}
73+
74+
interface RegistrationDataStatsInterface {
75+
month:
76+
| 'jan'
77+
| 'feb'
78+
| 'mar'
79+
| 'apr'
80+
| 'may'
81+
| 'jun'
82+
| 'jul'
83+
| 'aug'
84+
| 'sep'
85+
| 'oct'
86+
| 'nov'
87+
| 'dec'
88+
| null;
89+
users: number | null;
90+
organizations: number | null;
91+
}
92+
93+
interface RegistrationDataInterface {
94+
year: number;
95+
stats: RegistrationDataStatsInterface[];
96+
}
97+
98+
const [selectedRegistrationData, setSelectedRegistrationData] =
99+
useState<RegistrationDataStatsInterface[]>();
100+
const [allOrgsUsers, setAllOrgsUsers] = useState<AllOrgUsersInterface>({
101+
totalUsers: 0,
102+
organizations: [],
103+
});
104+
const [registrationData, setRegistrationData] =
105+
useState<RegistrationDataInterface[]>();
106+
const [selectedYear, setSelectedYear] = useState<number>();
107+
const [registrationYears, setRegistrationYears] = useState<number[]>();
108+
109+
const [getAllOrgUsers, { loading: getAllOrgUsersLoading }] =
110+
useLazyQuery(GET_ALL_ORG_USERS);
111+
const [getRegistrationStats, { loading: getRegistrationStatsLoading }] =
112+
useLazyQuery(GET_REGISTRATION_STATS);
113+
114+
useEffect(() => {
115+
getAllOrgUsers({
116+
fetchPolicy: 'network-only',
117+
onCompleted: (data) => {
118+
setAllOrgsUsers(data.getAllOrgUsers);
119+
},
120+
});
121+
122+
getRegistrationStats({
123+
fetchPolicy: 'network-only',
124+
onCompleted: (data) => {
125+
setRegistrationData(data.getRegistrationStats);
126+
},
127+
});
128+
}, [getAllOrgUsers, getRegistrationStats]);
129+
130+
useEffect(() => {
131+
const years = [new Date().getFullYear()];
132+
if (registrationData) {
133+
years.push(...registrationData.map((data) => data.year));
134+
const sanitizedYears = [...new Set(years)].sort((a, b) => b - a);
135+
setRegistrationYears(sanitizedYears);
136+
setSelectedYear(sanitizedYears[0]);
137+
return;
138+
}
139+
140+
const sanitizedYears = [...new Set(years)].sort((a, b) => b - a);
141+
setRegistrationYears(sanitizedYears);
142+
}, [registrationData]);
143+
144+
useEffect(() => {
145+
const months = [
146+
'jan',
147+
'feb',
148+
'mar',
149+
'apr',
150+
'may',
151+
'jun',
152+
'jul',
153+
'aug',
154+
'sep',
155+
'oct',
156+
'nov',
157+
'dec',
158+
];
159+
let data: RegistrationDataStatsInterface[] = [
160+
{
161+
month: null,
162+
users: 0,
163+
organizations: 0,
164+
},
165+
...months.map((month) => ({
166+
month: month as RegistrationDataStatsInterface['month'],
167+
users: null,
168+
organizations: null,
169+
})),
170+
];
171+
if (registrationData) {
172+
const tempData = registrationData.find(
173+
(data) => data.year === selectedYear,
174+
);
175+
if (tempData && tempData.stats.length) data = tempData.stats;
176+
}
177+
setSelectedRegistrationData(data);
178+
}, [selectedYear, registrationData]);
179+
180+
if (getAllOrgUsersLoading || getRegistrationStatsLoading) {
181+
return <div>Loading...</div>;
182+
}
183+
184+
return (
185+
<div>
186+
<div className="flex justify-between w-full items-center">
187+
<h2 className="font-semibold">User growth Over Time</h2>
188+
<div className="flex md:hidden items-center grouped-input border rounded border-primary/80 overflow-hidden pr-2 dark:bg-dark-tertiary">
189+
<select
190+
value={selectedYear}
191+
onChange={(event) => setSelectedYear(Number(event.target.value))}
192+
className="w-full px-2 py-[6px] text-xs text-black dark:text-white outline-none bg-inherit"
193+
>
194+
{registrationYears?.map((year) => (
195+
<option
196+
key={year}
197+
value={year}
198+
onClick={() => setSelectedYear(year)}
199+
className={`${
200+
selectedYear === year ? 'bg-primary' : 'hover:bg-primary/50'
201+
} cursor-pointer px-3 py-2 rounded-[4px] leading-3`}
202+
>
203+
{year}
204+
</option>
205+
))}
206+
</select>
207+
</div>
208+
</div>
209+
<ResponsiveContainer
210+
className="-ml-6 xmd:-ml-4 text-[.82rem] xmd:text-[.88rem] md:text-[.95rem] capitalize"
211+
width="100%"
212+
height={
213+
// eslint-disable-next-line no-nested-ternary
214+
window.innerWidth > 700 ? 350 : window.innerWidth > 500 ? 300 : 250
215+
}
216+
>
217+
<LineChart
218+
data={selectedRegistrationData}
219+
margin={{ top: 10, right: 10, bottom: 10, left: 10 }}
220+
>
221+
<CartesianGrid
222+
vertical={false}
223+
stroke="#5C5656"
224+
strokeDasharray="4 4"
225+
/>
226+
<Tooltip
227+
wrapperStyle={{
228+
padding: 0,
229+
margin: 0,
230+
fontSize: '.85rem',
231+
}}
232+
itemStyle={{
233+
marginInline: '.2rem', // Remove margin from each item
234+
lineHeight: '1', // Control line spacing if needed
235+
}}
236+
labelStyle={{
237+
display: 'none',
238+
}}
239+
/>
240+
<Legend
241+
wrapperStyle={{
242+
paddingTop: '1.5rem',
243+
}}
244+
/>
245+
<XAxis dataKey="month" dy={10} />
246+
<YAxis dx={-10} />
247+
<Line type="monotone" dataKey="users" stroke="#0C7640" />
248+
<Line type="monotone" dataKey="organizations" stroke="#8667f0" />
249+
</LineChart>
250+
</ResponsiveContainer>
251+
<div className="hidden md:flex flex-col items-center gap-y-4 ml-6 lg:ml-16 lg:mr-12 text-[.9rem]">
252+
{registrationYears?.map((year) => (
253+
<button
254+
key={year}
255+
type="button"
256+
onClick={() => setSelectedYear(year)}
257+
className={`${
258+
selectedYear === year ? 'bg-primary' : 'hover:bg-primary/50'
259+
} cursor-pointer px-3 py-2 rounded-[4px] leading-3`}
260+
>
261+
{year}
262+
</button>
263+
))}
264+
</div>
265+
</div>
266+
);
267+
}

src/Chart/UsersChart.tsx

-113
This file was deleted.

0 commit comments

Comments
 (0)