Skip to content

Commit fb972a4

Browse files
committed
user Growth overtime chart
1 parent 548376c commit fb972a4

File tree

3 files changed

+279
-127
lines changed

3 files changed

+279
-127
lines changed

src/Chart/LineChart.tsx

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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 const 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
201+
? 'bg-primary'
202+
: 'hover:bg-primary/50'
203+
} cursor-pointer px-3 py-2 rounded-[4px] leading-3`}
204+
>
205+
{year}
206+
</option>
207+
))}
208+
</select>
209+
</div>
210+
</div>
211+
<ResponsiveContainer
212+
className="-ml-6 xmd:-ml-4 text-[.82rem] xmd:text-[.88rem] md:text-[.95rem] capitalize"
213+
width="100%"
214+
height={
215+
// eslint-disable-next-line no-nested-ternary
216+
window.innerWidth > 700 ? 350 : window.innerWidth > 500 ? 300 : 250
217+
}
218+
>
219+
<LineChart
220+
data={selectedRegistrationData}
221+
margin={{ top: 10, right: 10, bottom: 10, left: 10 }}
222+
>
223+
<CartesianGrid
224+
vertical={false}
225+
stroke="#5C5656"
226+
strokeDasharray="4 4"
227+
/>
228+
<Tooltip
229+
wrapperStyle={{
230+
padding: 0,
231+
margin: 0,
232+
fontSize: '.85rem',
233+
}}
234+
itemStyle={{
235+
marginInline: '.2rem', // Remove margin from each item
236+
lineHeight: '1', // Control line spacing if needed
237+
}}
238+
labelStyle={{
239+
display: 'none',
240+
}}
241+
/>
242+
<Legend
243+
wrapperStyle={{
244+
paddingTop: '1.5rem',
245+
}}
246+
/>
247+
<XAxis dataKey="month" dy={10} />
248+
<YAxis dx={-10} />
249+
<Line type="monotone" dataKey="users" stroke="#0C7640" />
250+
<Line type="monotone" dataKey="organizations" stroke="#8667f0" />
251+
</LineChart>
252+
</ResponsiveContainer>
253+
<div className="hidden md:flex flex-col items-center gap-y-4 ml-6 lg:ml-16 lg:mr-12 text-[.9rem]">
254+
{registrationYears?.map((year) => (
255+
<button
256+
key={year}
257+
type="button"
258+
onClick={() => setSelectedYear(year)}
259+
className={`${
260+
selectedYear === year
261+
? 'bg-primary'
262+
: 'hover:bg-primary/50'
263+
} cursor-pointer px-3 py-2 rounded-[4px] leading-3`}
264+
>
265+
{year}
266+
</button>
267+
))}
268+
</div>
269+
</div>
270+
);
271+
};

src/Chart/UsersChart.tsx

Lines changed: 0 additions & 113 deletions
This file was deleted.

0 commit comments

Comments
 (0)