Skip to content

Commit 809f20b

Browse files
Enable admin to view all programs with user breakdown per program-#422
1 parent 4321d94 commit 809f20b

File tree

2 files changed

+195
-9
lines changed

2 files changed

+195
-9
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import React from 'react';
2+
import { gql, useQuery } from '@apollo/client';
3+
import DataTable from './DataTable';
4+
import Dialog from '@mui/material/Dialog';
5+
import DialogContent from '@mui/material/DialogContent';
6+
import DialogTitle from '@mui/material/DialogTitle';
7+
import { styled } from '@mui/material/styles';
8+
9+
interface User {
10+
email: string;
11+
role?: string;
12+
team?: {
13+
cohort?: {
14+
program?: {
15+
name: string;
16+
};
17+
};
18+
};
19+
organizations: string[];
20+
}
21+
22+
interface ProgramUsersModalProps {
23+
programId: string;
24+
programName: string;
25+
defaultProgram?: string;
26+
isOpen: boolean;
27+
onClose: () => void;
28+
}
29+
30+
interface CellProps {
31+
value: string;
32+
}
33+
34+
// Styled components for dark mode support
35+
const StyledDialog = styled(Dialog)(({ theme }) => ({
36+
'& .MuiDialog-paper': {
37+
backgroundColor: 'transparent',
38+
boxShadow: 'none',
39+
},
40+
}));
41+
42+
const StyledDialogTitle = styled(DialogTitle)({
43+
padding: '16px 24px',
44+
margin: 0,
45+
});
46+
47+
const StyledDialogContent = styled(DialogContent)({
48+
padding: '20px 24px',
49+
});
50+
51+
const GET_ALL_USERS = gql`
52+
query GetAllUsers($orgToken: String) {
53+
getAllUsers(orgToken: $orgToken) {
54+
email
55+
role
56+
team {
57+
cohort {
58+
program {
59+
name
60+
}
61+
}
62+
}
63+
organizations
64+
}
65+
}
66+
`;
67+
68+
export function ProgramUsersModal({ programId, isOpen, onClose,defaultProgram = 'default', programName }: ProgramUsersModalProps) {
69+
const { data, loading, error } = useQuery(GET_ALL_USERS, {
70+
variables: {
71+
orgToken: localStorage.getItem('orgToken'),
72+
},
73+
skip: !isOpen,
74+
});
75+
76+
const programUsers = data?.getAllUsers.filter(
77+
(user: User) => user.team?.cohort?.program?.name === programName || (user.team === null && programName === defaultProgram)
78+
) || [];
79+
80+
const columns = [
81+
{
82+
Header: 'Email',
83+
accessor: 'email',
84+
Cell: ({ value }: CellProps) => (
85+
<div className="flex items-center">
86+
<span className="hidden ml-2 md:inline-block h-8 w-8 rounded-full overflow-hidden bg-gray-100 dark:bg-gray-700">
87+
<svg className="h-full w-full text-gray-300 dark:text-gray-500" fill="currentColor" viewBox="0 0 24 24">
88+
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
89+
</svg>
90+
</span>
91+
<span className="ml-3 dark:text-gray-200">{value}</span>
92+
</div>
93+
),
94+
},
95+
{
96+
Header: 'Role',
97+
accessor: 'role',
98+
Cell: ({ value }: CellProps) => (
99+
<span className="capitalize dark:text-gray-200">{value || 'N/A'}</span>
100+
),
101+
},
102+
{
103+
Header: 'Organization',
104+
accessor: 'organizations',
105+
Cell: ({ value }: { value: string[] }) => (
106+
<span className="dark:text-gray-200">{value.join(', ')}</span>
107+
),
108+
},
109+
];
110+
111+
const renderContent = () => {
112+
if (loading) {
113+
return (
114+
<div className="flex justify-center items-center h-48">
115+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
116+
</div>
117+
);
118+
}
119+
120+
if (error) {
121+
return (
122+
<div className="text-red-500 text-center">
123+
Error loading users. Please try again.
124+
</div>
125+
);
126+
}
127+
128+
if (programUsers.length === 0) {
129+
return (
130+
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
131+
No users found for this program
132+
</div>
133+
);
134+
}
135+
136+
return (
137+
<div className="dark:bg-gray-800">
138+
<DataTable
139+
data={programUsers}
140+
columns={columns}
141+
title="Program Users"
142+
/>
143+
</div>
144+
);
145+
};
146+
147+
return (
148+
<StyledDialog
149+
open={isOpen}
150+
onClose={onClose}
151+
maxWidth="md"
152+
fullWidth
153+
>
154+
<div className="bg-white dark:bg-gray-800 rounded-t-lg">
155+
<StyledDialogTitle className="text-gray-900 dark:text-white border-b dark:border-gray-700">
156+
{programName} - Users
157+
</StyledDialogTitle>
158+
<StyledDialogContent className="bg-white dark:bg-gray-800">
159+
{renderContent()}
160+
</StyledDialogContent>
161+
</div>
162+
</StyledDialog>
163+
);
164+
}

src/containers/admin-dashBoard/Programs.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CreateProgramModal from './CreateProgramModal';
1010
import DeleteProgramModal from './DeleteProgramModal';
1111
import UpdateProgramModal from './UpdateProgramModal';
1212
import TtlSkeleton from '../../Skeletons/ttl.skeleton';
13+
import { ProgramUsersModal } from '../../components/ProgramUsersModal';
1314

1415
export interface Program {
1516
id: string;
@@ -53,13 +54,32 @@ function ActionButtons({
5354
setCurrentProgram,
5455
setUpdateProgramModal,
5556
setDeleteProgramModal,
57+
setViewUsersModal,
5658
...props
5759
}: any) {
5860
return (
5961
<div className="flex relative flex-row align-middle justify-center items-center">
62+
<div
63+
onClick={() => {
64+
const program = getData?.getAllPrograms[props.row.index];
65+
setCurrentProgram(program);
66+
setViewUsersModal(true);
67+
}}
68+
>
69+
<Icon
70+
icon="heroicons:eye"
71+
className="mr-2"
72+
width="25"
73+
height="25"
74+
cursor="pointer"
75+
color="#9e85f5"
76+
/>
77+
</div>
6078
<div
6179
data-testid="updateIcon"
80+
/* istanbul ignore next */
6281
onClick={() => {
82+
/* istanbul ignore next */
6383
const program = getData?.getAllPrograms[props.row.index];
6484
setCurrentProgram(program);
6585
setUpdateProgramModal(true);
@@ -76,9 +96,7 @@ function ActionButtons({
7696
</div>
7797
<div
7898
data-testid="deleteIcon"
79-
/* istanbul ignore next */
8099
onClick={() => {
81-
/* istanbul ignore next */
82100
const program = getData?.getAllPrograms[props.row.index];
83101
setCurrentProgram(program);
84102
setDeleteProgramModal(true);
@@ -120,6 +138,7 @@ function AdminPrograms() {
120138
const [createProgramModel, setCreateProgramModel] = useState(false);
121139
const [updateProgramModal, setUpdateProgramModal] = useState(false);
122140
const [deleteProgramModal, setDeleteProgramModal] = useState(false);
141+
const [viewUsersModal, setViewUsersModal] = useState(false);
123142
const [currentProgram, setCurrentProgram] = useState<Program | undefined>(
124143
undefined,
125144
);
@@ -131,7 +150,6 @@ function AdminPrograms() {
131150
{ Header: t('Manager'), accessor: 'manager' },
132151
{ Header: t('Organization'), accessor: 'organization' },
133152
{ Header: t('Description'), accessor: 'description' },
134-
135153
{
136154
Header: t('Actions'),
137155
accessor: '',
@@ -141,11 +159,12 @@ function AdminPrograms() {
141159
setCurrentProgram,
142160
setUpdateProgramModal,
143161
setDeleteProgramModal,
162+
setViewUsersModal,
144163
...props,
145164
}),
146165
},
147166
];
148-
/* istanbul ignore next */
167+
149168
const programListData = getData
150169
? getData.getAllPrograms.map(
151170
({
@@ -163,7 +182,7 @@ function AdminPrograms() {
163182
}),
164183
)
165184
: [{}];
166-
/* istanbul ignore next */
185+
167186
const removeModel = () => {
168187
const newState = !createProgramModel;
169188
setCreateProgramModel(newState);
@@ -181,7 +200,6 @@ function AdminPrograms() {
181200
<UpdateProgramModal
182201
data={getData}
183202
updateProgramModal={updateProgramModal}
184-
/* istanbul ignore next */
185203
currentProgram={currentProgram}
186204
removeModel={() => {
187205
setUpdateProgramModal(false);
@@ -190,15 +208,19 @@ function AdminPrograms() {
190208
/>
191209
<DeleteProgramModal
192210
deleteProgramModal={deleteProgramModal}
193-
/* istanbul ignore next */
194211
currentProgram={currentProgram}
195212
removeModel={() => {
196213
setDeleteProgramModal(false);
197214
}}
198215
refetch={getRefetch}
199216
/>
217+
<ProgramUsersModal
218+
programId={currentProgram?.id ?? ''}
219+
programName={currentProgram?.name ?? ''}
220+
isOpen={viewUsersModal}
221+
onClose={() => setViewUsersModal(false)}
222+
/>
200223
{/* =========================== End:: CreateProgramModel =============================== */}
201-
202224
<div className="bg-light-bg dark:bg-dark-frame-bg ">
203225
<div className="flex items-left pb-8">
204226
<div className="flex gap-2">
@@ -231,4 +253,4 @@ function AdminPrograms() {
231253
);
232254
}
233255

234-
export default AdminPrograms;
256+
export default AdminPrograms;

0 commit comments

Comments
 (0)