diff --git a/package-lock.json b/package-lock.json
index 172e4d9a0..f859dc9b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,7 +35,7 @@
"apollo-upload-client": "^17.0.0",
"autoprefixer": "^10.4.14",
"axios": "^1.6.1",
- "chart.js": "^4.3.2",
+ "chart.js": "^4.4.6",
"cleave.js": "^1.6.0",
"cloudinary": "^1.39.0",
"cloudinary-react": "^1.8.1",
@@ -85,7 +85,7 @@
"react-tooltip": "^4.5.1",
"react-widgets": "^5.8.4",
"reactjs-popup": "^2.0.5",
- "recharts": "^2.7.2",
+ "recharts": "^2.13.3",
"sheetjs-style": "^0.15.8",
"sinon": "^14.0.2",
"subscriptions-transport-ws": "^0.11.0",
@@ -4043,7 +4043,8 @@
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
- "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
+ "license": "MIT"
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
@@ -7197,9 +7198,10 @@
}
},
"node_modules/chart.js": {
- "version": "4.4.4",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz",
- "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==",
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
+ "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
+ "license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -19698,6 +19700,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
"integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
+ "license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
@@ -20276,14 +20279,15 @@
}
},
"node_modules/recharts": {
- "version": "2.12.7",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz",
- "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==",
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.3.tgz",
+ "integrity": "sha512-YDZ9dOfK9t3ycwxgKbrnDlRC4BHdjlY73fet3a0C1+qGMjXVZe6+VXmpOIIhzkje5MMEL8AN4hLIe4AMskBzlA==",
+ "license": "MIT",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
- "react-is": "^16.10.2",
+ "react-is": "^18.3.1",
"react-smooth": "^4.0.0",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
@@ -20310,11 +20314,6 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
- "node_modules/recharts/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
- },
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
diff --git a/package.json b/package.json
index a053d4731..ca823aa29 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"apollo-upload-client": "^17.0.0",
"autoprefixer": "^10.4.14",
"axios": "^1.6.1",
- "chart.js": "^4.3.2",
+ "chart.js": "^4.4.6",
"cleave.js": "^1.6.0",
"cloudinary": "^1.39.0",
"cloudinary-react": "^1.8.1",
@@ -107,7 +107,7 @@
"react-tooltip": "^4.5.1",
"react-widgets": "^5.8.4",
"reactjs-popup": "^2.0.5",
- "recharts": "^2.7.2",
+ "recharts": "^2.13.3",
"sheetjs-style": "^0.15.8",
"sinon": "^14.0.2",
"subscriptions-transport-ws": "^0.11.0",
diff --git a/src/assets/dash-event-icon.svg b/src/assets/dash-event-icon.svg
new file mode 100644
index 000000000..a7d30c74d
--- /dev/null
+++ b/src/assets/dash-event-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/multiple-users.svg b/src/assets/multiple-users.svg
new file mode 100644
index 000000000..8b94610db
--- /dev/null
+++ b/src/assets/multiple-users.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx
index 00238a8fc..d77ea4b3b 100644
--- a/src/components/Calendar.tsx
+++ b/src/components/Calendar.tsx
@@ -13,7 +13,7 @@ import { useLazyQuery, useMutation } from '@apollo/client';
import { ADD_EVENT, EDIT_EVENT, CANCEL_EVENT } from '../Mutations/event';
import { GET_EVENTS } from '../queries/event.queries';
import moment from 'moment';
-import CalendarSkeleton from '../Skeletons/Calender.skeleton'
+import CalendarSkeleton from '../Skeletons/Calender.skeleton';
import { toast } from 'react-toastify';
import EventGuestList from './EventGuestList';
/* istanbul ignore next */
@@ -48,12 +48,12 @@ const Calendar = () => {
fetchPolicy: 'network-only',
});
} catch (error: any) {
- toast.error(error.message)
+ toast.error(error.message);
}
};
useEffect(() => {
- fetchData()
+ fetchData();
}, []);
const renderEvent = (e: EventContentArg) => (
@@ -91,10 +91,10 @@ const Calendar = () => {
authToken: localStorage.getItem('auth_token'),
orgToken: localStorage.getItem('orgToken'),
invitees: selectedGuests,
- }
+ },
})
.then(() => {
- fetchData()
+ fetchData();
toast.success('Event has been added!'); // {{ edit_1 }}
setNewEvent({
title: '',
@@ -104,11 +104,10 @@ const Calendar = () => {
timeToStart: '',
timeToEnd: '',
});
- setSelectedGuests([])
+ setSelectedGuests([]);
setTimeout(() => {
setAddEventModel(false);
}, 1000);
-
})
.catch((error) => {
toast.error(error.message); // Handle error if needed
@@ -124,8 +123,8 @@ const Calendar = () => {
};
//edit section
- const [editEvent] = useMutation(EDIT_EVENT)
- const [editEventModel, setEditEventModel] = useState(false)
+ const [editEvent] = useMutation(EDIT_EVENT);
+ const [editEventModel, setEditEventModel] = useState(false);
const [editedEvent, setEditedEvent] = useState({
id: '',
title: '',
@@ -137,9 +136,12 @@ const Calendar = () => {
});
const handleEditEventModel = async (e: EventInput) => {
- const event = data?.getEvents.find((event: any)=> event.id === e.event?.id)
+ const event = data?.getEvents.find(
+ (event: any) => event.id === e.event?.id,
+ );
if (event) {
- if(event.user !== JSON.parse(localStorage.getItem('auth')!).userId) return
+ if (event.user !== JSON.parse(localStorage.getItem('auth')!).userId)
+ return;
setEditedEvent((prev) => {
return {
...prev,
@@ -150,16 +152,16 @@ const Calendar = () => {
hostName: event.hostName,
timeToStart: event.timeToStart,
timeToEnd: event.timeToEnd,
- }
- })
- setSelectedGuests(event.invitees.map((invitee: any) => invitee.email))
+ };
+ });
+ setSelectedGuests(event.invitees.map((invitee: any) => invitee.email));
setEditEventModel(true);
}
};
const handleEditEvent = async (e: any) => {
- e.preventDefault()
- const { id, ...rest } = editedEvent
+ e.preventDefault();
+ const { id, ...rest } = editedEvent;
editEvent({
variables: {
eventId: id,
@@ -170,7 +172,7 @@ const Calendar = () => {
},
})
.then(() => {
- fetchData()
+ fetchData();
toast.success('Event has been updated!');
setEditedEvent({
id: '',
@@ -181,7 +183,7 @@ const Calendar = () => {
timeToStart: '',
timeToEnd: '',
});
- setSelectedGuests([])
+ setSelectedGuests([]);
setTimeout(() => {
setEditEventModel(false);
}, 1000);
@@ -189,34 +191,34 @@ const Calendar = () => {
.catch((error) => {
toast.error(error.message); // Handle error if needed
});
- }
+ };
const removeEditModel = (e: any) => {
- e.preventDefault()
- setSelectedGuests([])
- setEditEventModel(!editEventModel)
- }
+ e.preventDefault();
+ setSelectedGuests([]);
+ setEditEventModel(!editEventModel);
+ };
// delete section
- const [showDeleteModal, setShowDeleteModal] = useState(false)
- const [cancelEvent] = useMutation(CANCEL_EVENT)
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [cancelEvent] = useMutation(CANCEL_EVENT);
const handleDeleteConfirmation = (e: any) => {
- e.preventDefault()
- setShowDeleteModal(prev => !prev)
- }
+ e.preventDefault();
+ setShowDeleteModal((prev) => !prev);
+ };
const handleDelete = async (e: any) => {
- e.preventDefault()
+ e.preventDefault();
cancelEvent({
variables: {
eventId: editedEvent.id,
- authToken: localStorage.getItem('auth_token')
+ authToken: localStorage.getItem('auth_token'),
},
})
.then(() => {
- fetchData()
- toast.success('Event cancelled successfully')
+ fetchData();
+ toast.success('Event cancelled successfully');
setEditedEvent({
id: '',
title: '',
@@ -226,23 +228,24 @@ const Calendar = () => {
timeToStart: '',
timeToEnd: '',
});
- setSelectedGuests([])
+ setSelectedGuests([]);
setTimeout(() => {
- setShowDeleteModal(false)
+ setShowDeleteModal(false);
setEditEventModel(false);
}, 1000);
- }
- )
- .catch(err => {
- toast.error(err.message)
})
- }
+ .catch((err) => {
+ toast.error(err.message);
+ });
+ };
return (
<>
@@ -366,11 +369,14 @@ const Calendar = () => {
{showTraineeDropdown ? '-' : '+'}
- {showTraineeDropdown ?
+ {showTraineeDropdown ? (
: ''}
+ />
+ ) : (
+ ''
+ )}
@@ -381,7 +387,10 @@ const Calendar = () => {
>
{t('cancel')}
-
@@ -391,8 +400,10 @@ const Calendar = () => {
@@ -448,7 +459,11 @@ const Calendar = () => {
className=" dark:bg-dark-tertiary dark:text-white border border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full"
placeholderText={t('Start Date')}
style={{ marginRight: '10px' }}
- selected={editedEvent.start ? new Date(editedEvent.start) : new Date()}
+ selected={
+ editedEvent.start
+ ? new Date(editedEvent.start)
+ : new Date()
+ }
onChange={(start: any) =>
setEditedEvent({
...editedEvent,
@@ -465,8 +480,12 @@ const Calendar = () => {
className="dark:bg-dark-tertiary dark:text-white border border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full"
placeholderText={t('End Date')}
style={{ marginRight: '10px' }}
- selected={editedEvent.end ? new Date(editedEvent.end) : new Date()}
- onChange={(end: any) => setEditedEvent({ ...editedEvent, end })}
+ selected={
+ editedEvent.end ? new Date(editedEvent.end) : new Date()
+ }
+ onChange={(end: any) =>
+ setEditedEvent({ ...editedEvent, end })
+ }
/>
@@ -519,11 +538,14 @@ const Calendar = () => {
{showTraineeDropdown ? '-' : '+'}
- {showTraineeDropdown ?
+ {showTraineeDropdown ? (
: ''}
+ />
+ ) : (
+ ''
+ )}
@@ -534,11 +556,19 @@ const Calendar = () => {
>
{t('cancel')}
-
-
handleDeleteConfirmation(e)}>
+
+ handleDeleteConfirmation(e)}
+ >
{t('Delete')}
-
+
{t('save')}
@@ -549,8 +579,10 @@ const Calendar = () => {
@@ -559,16 +591,26 @@ const Calendar = () => {
-
Please confirm the cancellation of event {editedEvent.title}.
+
+ Please confirm the cancellation of event{' '}
+ {editedEvent.title}.
+
setShowDeleteModal(prev => !prev)}
+ onClick={(e) => setShowDeleteModal((prev) => !prev)}
>
{t('cancel')}
- handleDelete(e)}>
+ handleDelete(e)}
+ >
{t('Delete')}
@@ -579,34 +621,36 @@ const Calendar = () => {
{t('Calendar')}
- {JSON.parse(localStorage.getItem('auth')!).role !== "trainee" ?
-
- {t('Add event')}
-
- :''}
+ {JSON.parse(localStorage.getItem('auth')!).role !== 'trainee' ? (
+
+ {t('Add event')}
+
+ ) : (
+ ''
+ )}
{loading ? (
) : (
-
({
- id: event.id,
- end: moment(event.end).add({days:1}).format('YYYY-MM-DD'),
- start: moment(event.start).format('YYYY-MM-DD'),
- hostName: event.hostName,
- timeToStart: event.timeToStart,
- title: event.title,
- timeToEnd: event.timeToEnd,
- allDay: true,
- }))}
- plugins={[dayGridPlugin, interactionPlugin]}
- initialView="dayGridMonth"
- eventClick={handleEditEventModel}
- />
+ ({
+ id: event.id,
+ end: moment(event.end).add({ days: 1 }).format('YYYY-MM-DD'),
+ start: moment(event.start).format('YYYY-MM-DD'),
+ hostName: event.hostName,
+ timeToStart: event.timeToStart,
+ title: event.title,
+ timeToEnd: event.timeToEnd,
+ allDay: true,
+ }))}
+ plugins={[dayGridPlugin, interactionPlugin]}
+ initialView="dayGridMonth"
+ eventClick={handleEditEventModel}
+ />
)}
>
diff --git a/src/components/OrgStatusSymbol.tsx b/src/components/OrgStatusSymbol.tsx
new file mode 100644
index 000000000..8e3072712
--- /dev/null
+++ b/src/components/OrgStatusSymbol.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+interface PropsInterface {
+ type: 'active' | 'pending' | 'rejected';
+ label?: boolean;
+}
+
+function OrgStatusSymbol({ type, label }: PropsInterface) {
+ const colorClasses = {
+ active: 'border-[#11AF0E] bg-[#11AF0E]',
+ pending: 'border-[#FFA500] bg-[#FFA500]',
+ rejected: 'border-[#C30909] bg-[#C30909]',
+ };
+
+ const selectedColor = colorClasses[type];
+
+ return (
+
+ );
+}
+
+export default OrgStatusSymbol;
diff --git a/src/components/Organizations.tsx b/src/components/Organizations.tsx
index 1410ca4c7..7342a6c90 100644
--- a/src/components/Organizations.tsx
+++ b/src/components/Organizations.tsx
@@ -14,9 +14,13 @@ import OrgSkeleton from '../Skeletons/Organization.skeleton';
import { DeleteOrganization } from '../Mutations/OrganisationMutations';
import { RegisterNewOrganization } from '../Mutations/OrganisationMutations';
import { AddOrganization } from '../Mutations/OrganisationMutations';
+import { GET_ORGANIZATIONS } from '../queries/organization.queries';
export interface Admin {
id: string;
+ profile: {
+ name: string
+ }
email: string;
}
export interface Organization {
@@ -24,24 +28,10 @@ export interface Organization {
name: string;
description: string;
admin: Admin;
+ status: 'active' | 'rejected' | 'pending';
[x: string]: any;
}
-export const getOrganizations = gql`
- query GetOrganizations {
- getOrganizations {
- id
- name
- description
- admin {
- id
- email
- }
- status
- }
- }
-`;
-
function ActionButtons({
getData,
setData,
@@ -143,7 +133,7 @@ const Organizations = () => {
loading: boolean;
error?: any;
refetch: Function;
- } = useQuery(getOrganizations);
+ } = useQuery(GET_ORGANIZATIONS);
const [createOrganizationModel, setCreateOrganizationModel] = useState(false);
const [deleteOrganizationModel, setDeleteOrganizationModel] = useState(false);
@@ -540,15 +530,15 @@ const Organizations = () => {
- {getLoading ? (
-
- ) : (
-
- )}
+ {getLoading ? (
+
+ ) : (
+
+ )}
diff --git a/src/components/icons/MultipleLogins.tsx b/src/components/icons/MultipleLogins.tsx
new file mode 100644
index 000000000..52b0b4c0d
--- /dev/null
+++ b/src/components/icons/MultipleLogins.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+function MultipleLogins({ color }: { color: string }) {
+ return (
+
+ );
+}
+
+export default MultipleLogins;
diff --git a/src/pages/SupAdDashboard.tsx b/src/pages/SupAdDashboard.tsx
index 593eb3585..ae0f9d5ff 100644
--- a/src/pages/SupAdDashboard.tsx
+++ b/src/pages/SupAdDashboard.tsx
@@ -1,26 +1,545 @@
-import React from 'react';
+import React, { useEffect, useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
-import Chart from '../components/Chart';
-import Card from '../components/Card';
+import { GlobeAltIcon, HomeIcon, UsersIcon } from '@heroicons/react/solid';
+import { BiCalendarStar } from 'react-icons/bi';
+import { AtSign, MapPin } from 'lucide-react';
+import {
+ CartesianGrid,
+ Legend,
+ Line,
+ LineChart,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from 'recharts';
+import { GoOrganization } from 'react-icons/go';
+import { GrGroup } from 'react-icons/gr';
+import { RiAdminLine } from 'react-icons/ri';
+import { useLazyQuery, useQuery } from '@apollo/client';
+import { format } from 'date-fns';
+import { ThemeContext } from '../hook/ThemeProvider';
import useDocumentTitle from '../hook/useDocumentTitle';
-import Comingsoon from './Comingsoon';
+import OrgStatusSymbol from '../components/OrgStatusSymbol';
+import MultipleLogins from '../components/icons/MultipleLogins';
+import { Organization } from '../components/Organizations';
+import {
+ GET_ORGANIZATIONS,
+ GET_ALL_ORG_USERS,
+ GET_REGISTRATION_STATS,
+} from '../queries/organization.queries';
+import { GET_EVENTS } from '../queries/event.queries';
+import { UserInterface } from './TraineeAttendanceTracker';
+
+interface EventsInterface {
+ title: string;
+ start: string;
+ end: string;
+ timeToStart: string;
+ timeToEnd: string;
+ hostName: string;
+}
+
+interface AllOrgUsersInterface {
+ totalUsers: number;
+ organizations: {
+ organization: Organization;
+ members: UserInterface[];
+ loginsCount: number;
+ monthPercentage: number;
+ recentLocation: string | null;
+ }[];
+}
+
+interface RegistrationDataStatsInterface {
+ month:
+ | 'jan'
+ | 'feb'
+ | 'mar'
+ | 'apr'
+ | 'may'
+ | 'jun'
+ | 'jul'
+ | 'aug'
+ | 'sep'
+ | 'oct'
+ | 'nov'
+ | 'dec'
+ | null;
+ users: number | null;
+ organizations: number | null;
+}
+interface RegistrationDataInterface {
+ year: number;
+ stats: RegistrationDataStatsInterface[];
+}
function SupAdDashboard() {
useDocumentTitle('Dashboard');
const { t } = useTranslation();
+ const { colorTheme } = useContext(ThemeContext);
+ const [allOrganizationData, setAllOrganizationData] = useState<
+ Organization[]
+ >([]);
+ const [upcomingEvents, setUpcomingEvents] = useState([]);
+ const [allOrgsUsers, setAllOrgsUsers] = useState({
+ totalUsers: 0,
+ organizations: [],
+ });
+
+ const [registrationData, setRegistrationData] =
+ useState();
+
+ const [selectedRegistrationData, setSelectedRegistrationData] =
+ useState();
+
+ const [selectedYear, setSelectedYear] = useState();
+ const [registrationYears, setRegistrationYears] = useState();
+
+ const {
+ data: organizationsData,
+ loading: getOrganizationsDataLoading,
+ error: getOrganizationsDataError,
+ refetch: getOrganizationsDataRefetch,
+ }: {
+ data?: {
+ getOrganizations: Organization[];
+ };
+ loading: boolean;
+ error?: any;
+ refetch: Function;
+ } = useQuery(GET_ORGANIZATIONS);
+
+ const [getEvents, { loading: getEventsDataLoading }] =
+ useLazyQuery(GET_EVENTS);
+
+ const [getAllOrgUsers, { loading: getAllOrgUsersLoading }] =
+ useLazyQuery(GET_ALL_ORG_USERS);
+
+ const [getRegistrationStats, { loading: getRegistrationStatsLoading }] =
+ useLazyQuery(GET_REGISTRATION_STATS);
+
+ useEffect(() => {
+ if (organizationsData) {
+ setAllOrganizationData(organizationsData.getOrganizations);
+ }
+ }, [organizationsData]);
+
+ useEffect(() => {
+ getEvents({
+ variables: {
+ authToken: localStorage.getItem('auth_token'),
+ },
+ fetchPolicy: 'network-only',
+ onCompleted: (data) => {
+ setUpcomingEvents(data.getEvents);
+ },
+ });
+ }, []);
+ useEffect(() => {
+ getAllOrgUsers({
+ fetchPolicy: 'network-only',
+ onCompleted: (data) => {
+ setAllOrgsUsers(data.getAllOrgUsers);
+ },
+ });
+ }, []);
+ useEffect(() => {
+ getRegistrationStats({
+ fetchPolicy: 'network-only',
+ onCompleted: (data) => {
+ setRegistrationData(data.getRegistrationStats);
+ },
+ });
+ }, []);
+
+ useEffect(() => {
+ const years = [new Date().getFullYear()];
+ if (registrationData) {
+ years.push(...registrationData.map((data) => data.year));
+ const sanitizedYears = [...new Set(years)].sort((a, b) => b - a);
+ setRegistrationYears(sanitizedYears);
+ setSelectedYear(sanitizedYears[0]);
+ return;
+ }
+
+ const sanitizedYears = [...new Set(years)].sort((a, b) => b - a);
+
+ setRegistrationYears(years);
+ }, [registrationData]);
+
+ useEffect(() => {
+ const months = [
+ 'jan',
+ 'feb',
+ 'mar',
+ 'apr',
+ 'may',
+ 'jun',
+ 'jul',
+ 'aug',
+ 'sep',
+ 'oct',
+ 'nov',
+ 'dec',
+ ];
+ let data: RegistrationDataStatsInterface[] = [
+ {
+ month: null,
+ users: 0,
+ organizations: 0,
+ },
+ ...months.map((month) => ({
+ month: month as RegistrationDataStatsInterface['month'],
+ users: null,
+ organizations: null,
+ })),
+ ];
+ if (registrationData) {
+ const tempData = registrationData.find(
+ (data) => data.year === selectedYear,
+ );
+ if (tempData && tempData.stats.length) data = tempData.stats;
+ }
+ setSelectedRegistrationData(data);
+ }, [selectedYear]);
+
return (
-
-
-
- {/*
-
-
-
-
-
-
*/}
-
-
+
+
+
+
+
+
+ Organisations
+
+
+
+
+ {allOrganizationData.length || '00'}
+
+
+
+
+
+
+
+ {allOrganizationData
+ .filter((org) => org.status.toLowerCase() === 'active')
+ .length.toString()
+ .padStart(2, '0') || '00'}
+
+
+
+
+
+
+
+ {allOrganizationData
+ .filter((org) => org.status.toLowerCase() === 'pending')
+ .length.toString()
+ .padStart(2, '0') || '00'}
+
+
+
+
+
+
+
+ {allOrganizationData
+ .filter((org) => org.status.toLowerCase() === 'rejected')
+ .length.toString()
+ .padStart(2, '0') || '00'}
+
+
+
+
+
+
+
+
+
+ USERS
+
+
+
+
+ {allOrgsUsers.totalUsers.toString().padStart(2, '0')}
+
+
+
+
+
+
+
+ Domain
+
+
+
+ 00
+
+
+
+
+
+
Monthly Registrations
+
+
+
+
+
+
700
+ ? 350
+ : window.innerWidth > 500
+ ? 300
+ : 250
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {registrationYears?.map((year) => (
+ setSelectedYear(year)}
+ className={`${
+ selectedYear === year ? 'bg-primary' : 'hover:bg-primary/50'
+ } cursor-pointer px-3 py-2 rounded-[4px] leading-3`}
+ >
+ {year}
+
+ ))}
+
+
+
+
+
+
+
Recent Organization Requests
+
+
+
+
+
+
+
+ Name
+ |
+
+ Admin-Email
+ |
+
+ Status
+ |
+
+
+
+ {allOrganizationData
+ .slice(
+ allOrganizationData.length - 5,
+ allOrganizationData.length,
+ )
+ .map((org) => (
+
+ {org.name} |
+
+ {
+ // eslint-disable-next-line no-nested-ternary
+ org.admin
+ ? // eslint-disable-next-line no-nested-ternary
+ org.admin.email.length > 20
+ ? window.innerWidth > 530
+ ? `${org.admin.email.slice(0, 22)}..`
+ : `${org.admin.email.slice(0, 16)}..`
+ : org.admin.email
+ : '-'
+ }
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
Upcoming Events
+
+
+ {upcomingEvents.map((event) => (
+
+
+
+
+
{event.title} -
+
+
+ {event.hostName}
+
+
+
+
+ {event.timeToStart}
+ {event.timeToEnd && ` - ${event.timeToEnd}`}
+
+
+
+ {format(new Date(event.start), 'dd, MMM yyyy')}
+
+ -
+ {format(new Date(event.end), 'dd, MMM yyyy')}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
Organization Updates
+
+
+ {allOrgsUsers.organizations
+ .sort((a, b) => b.members.length - a.members.length)
+ .slice(0, 3)
+ .map((org) => (
+
+
+
+
+ {org.organization.name}
+
+
+ {format(new Date(), 'MMM, yyyy')}
+
+
+
+
+
+
+ {(org.organization.admin &&
+ org.organization.admin.profile &&
+ org.organization.admin.profile.name) ||
+ '-'}
+
+
+
+
+ {org.members.length}
+
+ {`${org.monthPercentage.toPrecision(2)}%`}
+
+
+
+
+ ))}
+
+
+
+
+
Today's Login Overview
+
+
+ {allOrgsUsers.organizations
+ .sort((a, b) => b.loginsCount - a.loginsCount)
+ .slice(0, 3)
+ .map((org) => (
+
+
+
+
+
+
{org.organization.name}
+
{org.loginsCount}
+
+
+
+
+
+
+
+ {org.recentLocation || 'unavailable'}
+
+ {org.recentLocation && (
+
+ (Recent login location)
+
+ )}
+
+
+
+ ))}
+
diff --git a/src/queries/organization.queries.tsx b/src/queries/organization.queries.tsx
new file mode 100644
index 000000000..5f2917162
--- /dev/null
+++ b/src/queries/organization.queries.tsx
@@ -0,0 +1,59 @@
+import { gql } from '@apollo/client';
+
+export const GET_ORGANIZATIONS = gql`
+ query GetOrganizations {
+ getOrganizations {
+ id
+ name
+ description
+ admin {
+ id
+ email
+ }
+ status
+ }
+ }
+`;
+export const GET_ALL_ORG_USERS = gql`
+ query GetAllOrgUsers {
+ getAllOrgUsers {
+ totalUsers
+ organizations {
+ organization {
+ id
+ name
+ description
+ admin {
+ id
+ email
+ profile {
+ name
+ }
+ }
+ status
+ }
+ members {
+ email
+ profile {
+ name
+ }
+ }
+ monthPercentage
+ loginsCount
+ recentLocation
+ }
+ }
+ }
+`;
+export const GET_REGISTRATION_STATS = gql`
+ query GetRegistrationStats {
+ getRegistrationStats {
+ year
+ stats {
+ month
+ users
+ organizations
+ }
+ }
+ }
+`;
diff --git a/tailwind.config.js b/tailwind.config.js
index 9ac45761f..7c55c1500 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -4,8 +4,9 @@ module.exports = {
theme: {
extend: {
screens: {
- xm:'360px',
+ xm: '360px',
sm: '375px',
+ xsm: '500px',
xmd: '600px',
md: '768px',
lg: '976px',
@@ -40,27 +41,31 @@ module.exports = {
// sans: ['PT Serif', 'serif'],
serif: ['Inter', 'serif'],
},
- extend: {
- borderRadius: {
- '4xl': '2rem',
- },
- keyframes: {
- wave: {
- '0%': { transform: 'rotate(0.0deg)' },
- '10%': { transform: 'rotate(14deg)' },
- '20%': { transform: 'rotate(-8deg)' },
- '30%': { transform: 'rotate(14deg)' },
- '40%': { transform: 'rotate(-4deg)' },
- '50%': { transform: 'rotate(10.0deg)' },
- '60%': { transform: 'rotate(0.0deg)' },
- '100%': { transform: 'rotate(0.0deg)' },
- },
+ borderRadius: {
+ '4xl': '2rem',
+ },
+ keyframes: {
+ wave: {
+ '0%': { transform: 'rotate(0.0deg)' },
+ '10%': { transform: 'rotate(14deg)' },
+ '20%': { transform: 'rotate(-8deg)' },
+ '30%': { transform: 'rotate(14deg)' },
+ '40%': { transform: 'rotate(-4deg)' },
+ '50%': { transform: 'rotate(10.0deg)' },
+ '60%': { transform: 'rotate(0.0deg)' },
+ '100%': { transform: 'rotate(0.0deg)' },
},
- animation: {
- 'waving-hand': 'wave 10s linear infinite',
+ 'ping-live': {
+ '0%': { transform: 'scale(0.8)', opacity: '1' },
+ '50%': { transform: 'scale(1)', opacity: '0.7' },
+ '100%': { transform: 'scale(0.8)', opacity: '1' },
},
},
+ animation: {
+ 'waving-hand': 'wave 10s linear infinite',
+ 'ping-live': 'ping-live 1.5s ease-in-out infinite',
+ },
},
- plugins: [],
},
+ plugins: [],
};