Skip to content

Commit 28e3597

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

File tree

2 files changed

+186
-9
lines changed

2 files changed

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

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)