Skip to content

Commit 0f2c656

Browse files
authored
Merge pull request #139 from AtifKhokhar/126-auto-approved-to-admin-users-page
126 auto approved to admin users page
2 parents e715351 + 9475ad1 commit 0f2c656

25 files changed

+268
-89
lines changed

client/src/components/App/Admin/CreateBooking.tsx

+32-13
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import AdminLayout from './Layout/Layout';
1818
import Loading from '../../Assets/LoadingSpinner';
1919
import { OurButton } from '../../../styles/MaterialComponents';
2020

21-
import { getOffices, createBooking, getOffice } from '../../../lib/api';
21+
import { getOffices, createBooking, getOffice, getUser } from '../../../lib/api';
2222
import { formatError } from '../../../lib/app';
23-
import { OfficeSlot, OfficeWithSlots, Office } from '../../../types/api';
23+
import { OfficeSlot, OfficeWithSlots, Office, User } from '../../../types/api';
2424
import { validateEmail } from '../../../lib/emailValidation';
2525

2626
import CreateBookingStyles from './CreateBooking.styles';
@@ -43,13 +43,36 @@ const AdminCreateBooking: React.FC<RouteComponentProps> = () => {
4343
const [showReasonConfirmation, setShowReasonConfirmation] = useState(false);
4444
const [bookingReason, setBookingReason] = useState<string | undefined>();
4545
const [isAutoApprovedUser, setIsAutoApprovedUser] = useState<boolean>(false);
46+
const [searchedUser, setSearchedUser] = useState<User | undefined>();
4647

4748
// Helpers
4849
const findOffice = useCallback(
4950
(name: OfficeWithSlots['name']) => offices && offices.find((o) => o.name === name),
5051
[offices]
5152
);
5253

54+
const handleFetchUser = () => {
55+
setIsAutoApprovedUser(false);
56+
57+
if (user && config?.reasonToBookRequired) {
58+
// Get selected user
59+
getUser(email)
60+
.then((searchedUser) => setSearchedUser(searchedUser))
61+
.catch((err) => {
62+
// Handle errors
63+
setLoading(false);
64+
65+
dispatch({
66+
type: 'SET_ALERT',
67+
payload: {
68+
message: formatError(err),
69+
color: 'error',
70+
},
71+
});
72+
});
73+
}
74+
};
75+
5376
// Effects
5477
useEffect(() => {
5578
if (user) {
@@ -114,21 +137,13 @@ const AdminCreateBooking: React.FC<RouteComponentProps> = () => {
114137
}, [officeSlot]);
115138

116139
useEffect(() => {
117-
setIsAutoApprovedUser(false);
118-
119-
if (email.length > 0) {
120-
if (config?.autoApprovedEmails.includes(email)) {
121-
setIsAutoApprovedUser(true);
122-
}
140+
if (searchedUser?.autoApproved === true) {
141+
setIsAutoApprovedUser(true);
123142
}
124-
}, [email, config]);
143+
}, [searchedUser?.autoApproved]);
125144

126145
// Handlers
127146
const handleSubmit = (e?: React.FormEvent<HTMLFormElement>) => {
128-
// if (e) {
129-
// e.preventDefault();
130-
// }
131-
132147
// Validation
133148
if (email === '') {
134149
return dispatch({
@@ -205,6 +220,7 @@ const AdminCreateBooking: React.FC<RouteComponentProps> = () => {
205220
setParking(false);
206221
setBookingReason(undefined);
207222
setIsAutoApprovedUser(false);
223+
setSearchedUser(undefined);
208224

209225
// Show success alert
210226
dispatch({
@@ -261,6 +277,9 @@ const AdminCreateBooking: React.FC<RouteComponentProps> = () => {
261277
type="email"
262278
value={email}
263279
onChange={(e) => setEmail(e.target.value)}
280+
onBlur={() => {
281+
handleFetchUser();
282+
}}
264283
className="input"
265284
/>
266285
</div>

client/src/components/App/Admin/User.tsx

+46-7
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,20 @@ import { formatError } from '../../../lib/app';
2020
import { User, Office } from '../../../types/api';
2121

2222
import UserStyles from './User.styles';
23+
import { Checkbox, FormControlLabel } from '@material-ui/core';
2324

2425
// Component
2526
const UserAdmin: React.FC<RouteComponentProps<{ email: string }>> = (props) => {
2627
// Global state
2728
const { state, dispatch } = useContext(AppContext);
28-
const { user } = state;
29+
const { config, user } = state;
2930
const canEdit = user?.permissions.canEditUsers === true;
3031

3132
// Local state
3233
const [loading, setLoading] = useState(true);
3334
const [offices, setOffices] = useState<Office[] | undefined>();
3435
const [selectedUser, setSelectedUser] = useState<User | undefined>();
36+
const [autoApprovedSelected, setAutoApprovedSelected] = useState(false);
3537

3638
// Effects
3739
useEffect(() => {
@@ -65,8 +67,12 @@ const UserAdmin: React.FC<RouteComponentProps<{ email: string }>> = (props) => {
6567
if (user && selectedUser) {
6668
// Get all offices admin can manage
6769
setOffices(user.permissions.officesCanManageBookingsFor);
70+
71+
if (config?.reasonToBookRequired && selectedUser.autoApproved !== undefined) {
72+
setAutoApprovedSelected(selectedUser.autoApproved);
73+
}
6874
}
69-
}, [user, selectedUser, dispatch]);
75+
}, [user, selectedUser, dispatch, config?.reasonToBookRequired]);
7076

7177
useEffect(() => {
7278
if (offices) {
@@ -97,11 +103,20 @@ const UserAdmin: React.FC<RouteComponentProps<{ email: string }>> = (props) => {
97103
// Create/update user
98104
const role = selectedUser.role.name === 'System Admin' ? undefined : selectedUser.role;
99105

100-
putUser({
101-
email: selectedUser.email,
102-
quota: selectedUser.quota,
103-
role,
104-
})
106+
const putBody = config?.reasonToBookRequired
107+
? {
108+
email: selectedUser.email,
109+
quota: selectedUser.quota,
110+
role,
111+
autoApproved: selectedUser.autoApproved,
112+
}
113+
: {
114+
email: selectedUser.email,
115+
quota: selectedUser.quota,
116+
role,
117+
};
118+
119+
putUser(putBody)
105120
.then((updatedUser) => {
106121
// Update local state
107122
setSelectedUser(updatedUser);
@@ -261,6 +276,30 @@ const UserAdmin: React.FC<RouteComponentProps<{ email: string }>> = (props) => {
261276
/>
262277
</div>
263278

279+
{config?.reasonToBookRequired && (
280+
<div className="field">
281+
<FormControlLabel
282+
control={
283+
<Checkbox
284+
checked={autoApprovedSelected}
285+
onChange={(event) =>
286+
setSelectedUser(
287+
(selectedUser) =>
288+
selectedUser && {
289+
...selectedUser,
290+
autoApproved: event.target.checked,
291+
}
292+
)
293+
}
294+
name="checkedB"
295+
color="primary"
296+
/>
297+
}
298+
label="Auto Approved"
299+
/>
300+
</div>
301+
)}
302+
264303
{canEdit && (
265304
<div className="buttons">
266305
<OurButton

client/src/components/App/Admin/Users.tsx

+35-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import SearchIcon from '@material-ui/icons/Search';
2424
import TodayIcon from '@material-ui/icons/Today';
2525
import AddCircleIcon from '@material-ui/icons/AddCircle';
2626
import IconButton from '@material-ui/core/IconButton';
27+
import CheckIcon from '@material-ui/icons/Check';
28+
import ClearIcon from '@material-ui/icons/Clear';
2729

2830
import { AppContext } from '../../AppProvider';
2931
import Loading from '../../Assets/LoadingSpinner';
@@ -41,7 +43,7 @@ import { DialogTitle, Tooltip } from '@material-ui/core';
4143

4244
// Types
4345
type UserFilter = {
44-
user: 'active' | 'custom' | 'System Admin' | 'Office Admin';
46+
user: 'active' | 'custom' | 'System Admin' | 'Office Admin' | 'Auto Approved';
4547
email?: string;
4648
};
4749

@@ -57,8 +59,11 @@ const userFilterToQuery = (filter: UserFilter): UserQuery => {
5759
query.role = 'System Admin';
5860
} else if (filter.user === 'custom') {
5961
query.quota = 'custom';
62+
} else if (filter.user === 'Auto Approved') {
63+
query.autoApproved = 'true';
6064
}
6165

66+
console.log('query', query);
6267
return query;
6368
};
6469

@@ -81,14 +86,20 @@ const sortData = (data: User[], key: keyof User, order: SortOrder): User[] | und
8186
: data.sort((a, b) => a.role.name.localeCompare(b.role.name));
8287
}
8388

89+
if (key === 'autoApproved') {
90+
return order === 'desc'
91+
? data.sort((a, b) => Number(b.autoApproved as boolean) - Number(a.autoApproved as boolean))
92+
: data.sort((a, b) => Number(a.autoApproved as boolean) - Number(b.autoApproved as boolean));
93+
}
94+
8495
return data;
8596
};
8697

8798
// Component
8899
const Users: React.FC<RouteComponentProps> = () => {
89100
// Global state
90101
const { state, dispatch } = useContext(AppContext);
91-
const { user } = state;
102+
const { config, user } = state;
92103

93104
// Local state
94105
const [loading, setLoading] = useState(true);
@@ -177,7 +188,8 @@ const Users: React.FC<RouteComponentProps> = () => {
177188
user === 'active' ||
178189
user === 'System Admin' ||
179190
user === 'Office Admin' ||
180-
user === 'custom'
191+
user === 'custom' ||
192+
user === 'Auto Approved'
181193
) {
182194
setSelectedFilter((filter) => ({ ...filter, user }));
183195
}
@@ -261,6 +273,9 @@ const Users: React.FC<RouteComponentProps> = () => {
261273
<MenuItem value="System Admin">System Admins</MenuItem>
262274
<MenuItem value="Office Admin">Office Admins</MenuItem>
263275
<MenuItem value="custom">With custom quota</MenuItem>
276+
{config?.reasonToBookRequired && (
277+
<MenuItem value="Auto Approved">With auto approved</MenuItem>
278+
)}
264279
</Select>
265280
</FormControl>
266281
</div>
@@ -319,6 +334,17 @@ const Users: React.FC<RouteComponentProps> = () => {
319334
Role
320335
</TableSortLabel>
321336
</TableCell>
337+
{config?.reasonToBookRequired && (
338+
<TableCell className="table-header">
339+
<TableSortLabel
340+
active={sortBy === 'autoApproved'}
341+
direction={sortOrder}
342+
onClick={() => handleSort('autoApproved')}
343+
>
344+
Auto Approved
345+
</TableSortLabel>
346+
</TableCell>
347+
)}
322348
<TableCell className="table-header" />
323349
</TableRow>
324350
</TableHead>
@@ -329,6 +355,11 @@ const Users: React.FC<RouteComponentProps> = () => {
329355
<TableCell>{user.email}</TableCell>
330356
<TableCell>{user.quota}</TableCell>
331357
<TableCell>{user.role.name}</TableCell>
358+
{config?.reasonToBookRequired && (
359+
<TableCell>
360+
{user.autoApproved ? <CheckIcon /> : <ClearIcon color="disabled" />}
361+
</TableCell>
362+
)}
332363
<TableCell align="right">
333364
<Tooltip title={`Edit`} arrow>
334365
<IconButton onClick={() => navigate(`/admin/users/${user.email}`)}>
@@ -351,6 +382,7 @@ const Users: React.FC<RouteComponentProps> = () => {
351382
<TableCell />
352383
<TableCell />
353384
<TableCell />
385+
{config?.reasonToBookRequired && <TableCell />}
354386
</TableRow>
355387
)}
356388
</TableBody>

client/src/components/App/Home/MakeBooking.tsx

+24-4
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { AppContext } from '../../AppProvider';
2828
import BookButton from '../../Assets/BookButton';
2929
import { OurButton } from '../../../styles/MaterialComponents';
3030

31-
import { Booking, OfficeWithSlots } from '../../../types/api';
32-
import { createBooking, cancelBooking } from '../../../lib/api';
31+
import { Booking, OfficeWithSlots, User } from '../../../types/api';
32+
import { createBooking, cancelBooking, getUser } from '../../../lib/api';
3333
import { formatError } from '../../../lib/app';
3434
import { DATE_FNS_OPTIONS } from '../../../constants/dates';
3535

@@ -95,6 +95,7 @@ const MakeBooking: React.FC<Props> = (props) => {
9595
const [bookingParking, setBookingParking] = useState(false);
9696
const [bookingReason, setBookingReason] = useState<string | undefined>();
9797
const [isAutoApprovedUser, setIsAutoApprovedUser] = useState<boolean>(false);
98+
const [searchedUser, setSearchedUser] = useState<User | undefined>();
9899

99100
// Refs
100101
const reloadTimerRef = useRef<ReturnType<typeof setInterval> | undefined>();
@@ -275,13 +276,32 @@ const MakeBooking: React.FC<Props> = (props) => {
275276
}
276277
}, [bookings, office, user, weeks]);
277278

279+
useEffect(() => {
280+
if (user && config?.reasonToBookRequired) {
281+
// Get selected user
282+
getUser(user?.email)
283+
.then((searchedUser) => setSearchedUser(searchedUser))
284+
.catch((err) => {
285+
// Handle errors
286+
287+
dispatch({
288+
type: 'SET_ALERT',
289+
payload: {
290+
message: formatError(err),
291+
color: 'error',
292+
},
293+
});
294+
});
295+
}
296+
}, [user, dispatch, config?.reasonToBookRequired]);
297+
278298
useEffect(() => {
279299
if (user) {
280-
if (config?.autoApprovedEmails.includes(user?.email)) {
300+
if (searchedUser?.autoApproved) {
281301
setIsAutoApprovedUser(true);
282302
}
283303
}
284-
}, [user, config]);
304+
}, [searchedUser?.autoApproved, user]);
285305

286306
// Handlers
287307
const handleChangeWeek = (direction: 'forward' | 'backward') => {

client/src/context/stores.ts

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export type Config = {
2828
| { type: 'test' };
2929
emailRegex?: string;
3030
advancedBookingDays: number;
31-
autoApprovedEmails: string[];
3231
reasonToBookRequired: boolean;
3332
};
3433

client/src/lib/api.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ export const queryUsers = async (
5151
if (query.quota !== undefined) {
5252
url.searchParams.set('quota', query.quota);
5353
}
54+
55+
if (query.autoApproved !== undefined) {
56+
url.searchParams.set('autoApproved', query.autoApproved);
57+
}
58+
5459
if (query.emailPrefix !== undefined) {
5560
url.searchParams.set('emailPrefix', query.emailPrefix);
5661
}
@@ -109,6 +114,7 @@ export const putUser = async (user: {
109114
email: string;
110115
quota?: number | null;
111116
role?: DefaultRole | OfficeAdminRole;
117+
autoApproved?: boolean;
112118
}): Promise<User> => {
113119
const url = new URL(`users/${user.email}`, BASE_URL);
114120

@@ -117,7 +123,7 @@ export const putUser = async (user: {
117123
const response = await fetch(url.href, {
118124
method: 'PUT',
119125
headers,
120-
body: JSON.stringify({ quota: user.quota, role: user.role }),
126+
body: JSON.stringify({ quota: user.quota, role: user.role, autoApproved: user.autoApproved }),
121127
});
122128

123129
if (!response.ok) {

0 commit comments

Comments
 (0)