Skip to content

Commit 8c018f0

Browse files
committed
users table
1 parent 6bf641a commit 8c018f0

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

src/common/roles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export enum AppRoles {
77
TICKETS_MANAGER = "manage:tickets",
88
IAM_ADMIN = "admin:iam",
99
IAM_INVITE_ONLY = "invite:iam",
10+
USERS_ADMIN = "admin:users",
1011
}
1112
export const allAppRoles = Object.values(AppRoles).filter(
1213
(value) => typeof value === "string",

src/ui/Router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ScanTicketsPage } from './pages/tickets/ScanTickets.page';
1717
import { SelectTicketsPage } from './pages/tickets/SelectEventId.page';
1818
import { ViewTicketsPage } from './pages/tickets/ViewTickets.page';
1919
import { ManageIamPage } from './pages/iam/ManageIam.page';
20+
import { ScreenPage } from './pages/screen/Screen.page';
2021

2122
// Component to handle redirects to login with return path
2223
const LoginRedirect: React.FC = () => {
@@ -119,6 +120,10 @@ const authenticatedRouter = createBrowserRouter([
119120
path: '/tickets/manage/:eventId',
120121
element: <ViewTicketsPage />,
121122
},
123+
{
124+
path: '/users',
125+
element: <ScreenPage />,
126+
},
122127
// Catch-all route for authenticated users shows 404 page
123128
{
124129
path: '*',

src/ui/pages/screen/Screen.page.tsx

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { Text, Button, Table, Modal, Group, Transition, ButtonGroup } from '@mantine/core';
2+
import { useDisclosure } from '@mantine/hooks';
3+
import { notifications } from '@mantine/notifications';
4+
import { IconPlus, IconTrash } from '@tabler/icons-react';
5+
// import dayjs from 'dayjs';
6+
import React, { useEffect, useState } from 'react';
7+
// import { useNavigate } from 'react-router-dom';
8+
import { z } from 'zod';
9+
10+
// import { capitalizeFirstLetter } from './ManageEvent.page.js';
11+
import FullScreenLoader from '@ui/components/AuthContext/LoadingScreen';
12+
import { AuthGuard } from '@ui/components/AuthGuard';
13+
import { useApi } from '@ui/util/api';
14+
import { AppRoles } from '@common/roles.js';
15+
16+
// const repeatOptions = ['weekly', 'biweekly'] as const;
17+
18+
const userSchema = z.object({
19+
netid: z.string().min(1),
20+
firstName: z.string().min(1),
21+
middleName: z.string().optional(),
22+
lastName: z.string().min(1),
23+
sig: z.string(),
24+
// location: z.string(),
25+
// locationLink: z.optional(z.string().url()),
26+
// host: z.string(),
27+
// featured: z.boolean().default(false),
28+
// paidEventId: z.optional(z.string().min(1)),
29+
});
30+
31+
const usersSchema = z.array(userSchema);
32+
33+
// const requestSchema = baseSchema.extend({
34+
// repeats: z.optional(z.enum(repeatOptions)),
35+
// repeatEnds: z.string().optional(),
36+
// });
37+
38+
// const getEventSchema = requestSchema.extend({
39+
// id: z.string(),
40+
// upcoming: z.boolean().optional(),
41+
// });
42+
43+
export type User = z.infer<typeof userSchema>;
44+
export type Users = z.infer<typeof usersSchema>;
45+
46+
// export type EventGetResponse = z.infer<typeof getEventSchema>;
47+
// const getEventsSchema = z.array(getEventSchema);
48+
// export type EventsGetResponse = z.infer<typeof getEventsSchema>;
49+
50+
export const ScreenPage: React.FC = () => {
51+
const [userList, setUserList] = useState<Users>([]);
52+
const api = useApi('core');
53+
const [opened, { open, close }] = useDisclosure(false);
54+
// const [showPrevious, { toggle: togglePrevious }] = useDisclosure(false); // Changed default to false
55+
const [userRemoved, setRemoveUser] = useState<User | null>(null);
56+
// const navigate = useNavigate();
57+
58+
const renderTableRow = (user: User) => {
59+
// const shouldShow = event.upcoming || (!event.upcoming && showPrevious);
60+
61+
return (
62+
// <Transition mounted={shouldShow} transition="fade" duration={400} timingFunction="ease">
63+
<Transition mounted={true} transition="fade" duration={400} timingFunction="ease">
64+
{(styles) => (
65+
// <tr style={{ ...styles, display: shouldShow ? 'table-row' : 'none' }}>
66+
<tr style={{ ...styles, display: 'table-row' }}>
67+
<Table.Td>{user.netid}</Table.Td>
68+
<Table.Td>{user.firstName}</Table.Td>
69+
<Table.Td>{user.middleName}</Table.Td>
70+
<Table.Td>{user.lastName}</Table.Td>
71+
<Table.Td>{user.sig}</Table.Td>
72+
{/* <Table.Td>{dayjs(event.start).format('MMM D YYYY hh:mm')}</Table.Td>
73+
<Table.Td>{event.end ? dayjs(event.end).format('MMM D YYYY hh:mm') : 'N/A'}</Table.Td>
74+
<Table.Td>{event.location}</Table.Td>
75+
<Table.Td>{event.description}</Table.Td>
76+
<Table.Td>{event.host}</Table.Td>
77+
<Table.Td>{event.featured ? 'Yes' : 'No'}</Table.Td> */}
78+
{/* <Table.Td>{capitalizeFirstLetter(event.repeats || 'Never')}</Table.Td> */}
79+
{/* <Table.Td>
80+
<ButtonGroup>
81+
<Button component="a" href={`/events/edit/${event.id}`}>
82+
Edit
83+
</Button>
84+
<Button
85+
color="red"
86+
onClick={() => {
87+
setDeleteCandidate(event);
88+
open();
89+
}}
90+
>
91+
Delete
92+
</Button>
93+
</ButtonGroup>
94+
</Table.Td> */}
95+
</tr>
96+
)}
97+
</Transition>
98+
);
99+
};
100+
101+
useEffect(() => {
102+
const getUsers = async () => {
103+
// const response = await api.get('/api/v1/events');
104+
// const upcomingEvents = await api.get('/api/v1/events?upcomingOnly=true');
105+
// const upcomingEventsSet = new Set(upcomingEvents.data.map((x: EventGetResponse) => x.id));
106+
// const events = response.data;
107+
// events.sort((a: User, b: User) => {
108+
// return a.start.localeCompare(b.start);
109+
// });
110+
// const enrichedResponse = response.data.map((item: EventGetResponse) => {
111+
// if (upcomingEventsSet.has(item.id)) {
112+
// return { ...item, upcoming: true };
113+
// }
114+
// return { ...item, upcoming: false };
115+
// });
116+
117+
// prettier-ignore
118+
const mockUserResponse: Users = [
119+
{ netid: "ethanc12", firstName: "Ethan", middleName:"Yuting", lastName: "Chang", sig: "Infra"},
120+
{ netid: "johnd01", firstName: "John", lastName: "Doe", sig: "SIGMusic" },
121+
{ netid: "sarahg23", firstName: "Sarah", middleName: "Grace", lastName: "Gonzalez", sig: "SIGQuantum" },
122+
{ netid: "miker44", firstName: "Michael", lastName: "Roberts", sig: "SIGPlan" },
123+
{ netid: "annaw02", firstName: "Anna", middleName: "Marie", lastName: "Williams", sig: "SIGMobile" },
124+
{ netid: "chrisb19", firstName: "Christopher", lastName: "Brown", sig: "SIGCHI" },
125+
{ netid: "laurenp87", firstName: "Lauren", middleName: "Patricia", lastName: "Perez", sig: "SIGPwny" },
126+
{ netid: "ethanw12", firstName: "Ethan", lastName: "Wong", sig: "SIGEcom" },
127+
{ netid: "emilyh54", firstName: "Emily", lastName: "Hernandez", sig: "SIGRobotics" },
128+
{ netid: "kevink11", firstName: "Kevin", middleName: "Lee", lastName: "Kim", sig: "Infra" },
129+
{ netid: "juliel08", firstName: "Julie", lastName: "Lopez", sig: "SIGGRAPH" },
130+
{ netid: "mattt92", firstName: "Matthew", middleName: "Thomas", lastName: "Taylor", sig: "SIGtricity" },
131+
{ netid: "rachelb03", firstName: "Rachel", lastName: "Bell", sig: "SIGSYS" },
132+
{ netid: "stephenj45", firstName: "Stephen", middleName: "James", lastName: "Johnson", sig: "SIGAIDA" },
133+
{ netid: "ashleyc28", firstName: "Ashley", lastName: "Clark", sig: "SIGNLL" },
134+
{ netid: "briand77", firstName: "Brian", lastName: "Davis", sig: "SIGMA" },
135+
{ netid: "meganf65", firstName: "Megan", lastName: "Flores", sig: "SIGPolicy" },
136+
{ netid: "danielh04", firstName: "Daniel", lastName: "Hughes", sig: "SIGARCH" },
137+
{ netid: "victorc16", firstName: "Victor", middleName: "Charles", lastName: "Carter", sig: "SIGGLUG" },
138+
{ netid: "lindam29", firstName: "Linda", lastName: "Martinez", sig: "SIGMobile" },
139+
{ netid: "paulf31", firstName: "Paul", lastName: "Fisher", sig: "SIGMusic" },
140+
{ netid: "susana80", firstName: "Susan", middleName: "Ann", lastName: "Anderson", sig: "SIGPwny" },
141+
{ netid: "markl13", firstName: "Mark", lastName: "Lewis", sig: "SIGCHI" },
142+
{ netid: "carolynb59", firstName: "Carolyn", lastName: "Barnes", sig: "SIGSYS" },
143+
{ netid: "patrickh37", firstName: "Patrick", middleName: "Henry", lastName: "Hill", sig: "SIGQuantum" },
144+
{ netid: "nataliep71", firstName: "Natalie", lastName: "Price", sig: "SIGPolicy" },
145+
];
146+
setUserList(mockUserResponse);
147+
};
148+
getUsers();
149+
}, []);
150+
151+
const removeUser = async (netid: string) => {
152+
try {
153+
// await api.delete(`/api/v1/events/${eventId}`);
154+
setUserList((prevUsers) => prevUsers.filter((u) => u.netid !== netid));
155+
notifications.show({
156+
title: 'User removed',
157+
message: 'The user was successfully removed.',
158+
});
159+
close();
160+
} catch (error) {
161+
console.error(error);
162+
notifications.show({
163+
title: 'Error removing user',
164+
message: `${error}`,
165+
color: 'red',
166+
});
167+
}
168+
};
169+
170+
if (userList.length === 0) {
171+
return <FullScreenLoader />;
172+
}
173+
174+
return (
175+
// <AuthGuard resourceDef={{ service: 'core', validRoles: [AppRoles.USERS_ADMIN] }}>
176+
<AuthGuard resourceDef={{ service: 'core', validRoles: [AppRoles.IAM_ADMIN] }}>
177+
{userRemoved && (
178+
<Modal
179+
opened={opened}
180+
onClose={() => {
181+
setRemoveUser(null);
182+
close();
183+
}}
184+
title="Confirm action"
185+
>
186+
<Text>
187+
Are you sure you want to remove the user <i>{userRemoved?.netid}</i>?
188+
</Text>
189+
<hr />
190+
<Group>
191+
<Button
192+
leftSection={<IconTrash />}
193+
onClick={() => {
194+
removeUser(userRemoved?.netid);
195+
}}
196+
>
197+
Delete
198+
</Button>
199+
</Group>
200+
</Modal>
201+
)}
202+
{/* <div style={{ display: 'flex', columnGap: '1vw', verticalAlign: 'middle' }}>
203+
<Button
204+
leftSection={<IconPlus size={14} />}
205+
onClick={() => {
206+
navigate('/events/add');
207+
}}
208+
>
209+
New Calendar Event
210+
</Button>
211+
<Button onClick={togglePrevious}>
212+
{showPrevious ? 'Hide Previous Events' : 'Show Previous Events'}
213+
</Button>
214+
</div> */}
215+
<Table style={{ tableLayout: 'fixed', width: '100%' }} data-testid="users-table">
216+
<Table.Thead>
217+
<Table.Tr>
218+
<Table.Th>NetID</Table.Th>
219+
<Table.Th>First Name</Table.Th>
220+
<Table.Th>Middle Name</Table.Th>
221+
<Table.Th>Last Name</Table.Th>
222+
<Table.Th>Affiliated Special Interest Group</Table.Th>
223+
</Table.Tr>
224+
</Table.Thead>
225+
<Table.Tbody>{userList.map(renderTableRow)}</Table.Tbody>
226+
</Table>
227+
</AuthGuard>
228+
);
229+
};

0 commit comments

Comments
 (0)