diff --git a/.github/workflows/New-issue-create-card.yml b/.github/workflows/New-issue-create-card.yml deleted file mode 100644 index d64361822..000000000 --- a/.github/workflows/New-issue-create-card.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Create card for new issues -on: - issues: - types: [opened] -jobs: - createCard: - runs-on: ubuntu-latest - steps: - - name: Create or Update Project Card - uses: peter-evans/create-or-update-project-card@v2 - with: - project-name: VRMS - Active Project Board - column-name: New Issue Approval diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..7bfb475c1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +VRMS is governed by the [Hack for LA Code of Conduct](https://www.hackforla.org/code-of-conduct/) which applies to any interaction on our VRMS slack channel (inside the HackforLA Slack workspace), direct slack messages, github org or repository, or any other communication medium. diff --git a/backend/models/user.model.js b/backend/models/user.model.js index 1424a3753..5ff3ddacd 100644 --- a/backend/models/user.model.js +++ b/backend/models/user.model.js @@ -9,7 +9,11 @@ const userSchema = mongoose.Schema({ lastName: { type: String }, }, email: { type: String, unique: true }, - accessLevel: { type: String, default: "user" }, + accessLevel: { + type: String, + enum: ["user", "admin"], // restricts values to "user" and "admin" + default: "user" + }, createdDate: { type: Date, default: Date.now }, currentRole: { type: String }, // will remove but need to update check-in form desiredRole: { type: String }, // will remove but need to update check-in form diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js index d0c5506de..8d6c475f4 100644 --- a/client/src/api/UserApiService.js +++ b/client/src/api/UserApiService.js @@ -73,6 +73,23 @@ class UserApiService { alert('server not responding. Please try again.'); } } + + // Update user's access level (admin/user) + async updateUserAccessLevel(userToEdit, accessLevel) { + const url = `${this.baseUserUrl}${userToEdit._id}`; + const requestOptions = { + method: 'PATCH', + headers: this.headers, + body: JSON.stringify({ accessLevel }), + }; + + try { + return await fetch(url, requestOptions); + } catch (err) { + console.error('update access level error', err); + alert('server not responding. Please try again.'); + } + } } export default UserApiService; diff --git a/client/src/components/admin/reports/index.jsx b/client/src/components/admin/reports/index.jsx index 3e0baeea5..7c1242f54 100644 --- a/client/src/components/admin/reports/index.jsx +++ b/client/src/components/admin/reports/index.jsx @@ -1,6 +1,7 @@ import React, {useState} from 'react'; import Loading from '../donutChartLoading'; import DatePicker from 'react-datepicker'; +import { Box, Button, Table, TableHead, TableRow, TableCell, TableBody, Typography } from '@mui/material'; import 'react-datepicker/dist/react-datepicker.css'; import '../../../common/datepicker/index.scss'; import './index.scss'; @@ -26,6 +27,87 @@ const LocationTableReport = ({eventTypeStats, hackNightTypeStats, handleFiltered const [startTextDate, setStartTextDate] = useState(new Date().toLocaleDateString("en-US")); const [endTextDate, setEndTextDate] = useState(new Date().toLocaleDateString("en-US")); + const styles = { + adminTableReport: { + margin: '10px 0', + fontSize: '15px', + textAlign: 'center', + }, + statsSection: { + marginTop: '10px', + textAlign: 'right' + }, + tableHeader: { + fontSize: '15px', + fontWeight: 'bold', + backgroundColor: '#3d5a6c47', + margin: '20px 0 5px', + padding: '5px', + borderRadius: '7px 7px 0 0', + textAlign: 'center' + }, + adminTable: { + width: '100%', + marginTop: '10px', + borderCollapse: 'collapse', + border: 'none', + }, + tableCell: { + textAlign: 'center', + height: '30px', + border: 'none', + }, + lastRowCell: { + color: '#3D5A6C', + fontWeight: 'bold', + borderTop: '1px solid lightgray', + borderBottom: 'none', + textAlign: 'center', + height: '30px', + + }, + firstRowCell: { + color: '#3D5A6C', + fontWeight: 'bold', + borderBottom: 'none', + textAlign: 'center', + height: '30px', + + }, + filterButton: { + color: '#3D5A6C', + border: '1px solid lightgray', + borderRadius: '20px', + lineHeight: '1', + padding: '10px 20px', + margin: '0 auto', + transition: '.3s ease', + maxWidth: '120px', + minWidth: '120px', + height: 'auto', + fontFamily: "'Open Sans', sans-serif", + fontSize: '15px', + }, + calcButton: { + maxWidth: '150px', + minWidth: '150px', + }, + chartImage: { + maxWidth: '90px', + }, + timeDescription: { + fontSize: '13px', + textAlign: 'right', + marginTop: '10px', + }, + boldText: { + fontWeight: 'bold', + fontSize: '18px', + color: '#3D5A6C', + }, + + }; + prepareDataForReport( eventTypeStats, eventTypes, @@ -151,23 +233,19 @@ const LocationTableReport = ({eventTypeStats, hackNightTypeStats, handleFiltered } return ( -
+ {!isLoading ? ( -
- {isFilterButton && - - } + + )} {isDatepicker && -
-
-

Start

+ + + Start -
+ -
-

End

+ + End -
+ - -
+ + } -
-
- Stats calculated by: - {!isFiltered ? ( - all time - ) : ( - - {startTextDate} - - - {endTextDate} - - )} -
+ + + Stats calculated by: {!isFiltered ? 'all time' : `${startTextDate} - ${endTextDate}`} + -
All Events By Event Type
+ + All Events By Event Type + {isStatsByLocation ? ( - - - - {headerGroups.map(header => ( - - ))} - - - - - {dataForAllEventsReport.map((event) => ( - - - - - - - ))} - - {totalForAllEvents && - - - {totalForAllEvents.map((total, i) => ( - +
{header}
{event.location}{event.totalVolunteers}{event.totalVolunteerHours}{event.totalVolunteerAvgHours}
Total{total}
+ + + {headerGroups.map((header) => ( + {header} ))} - - } - -
- ) : ( -
No data for calculation stats
- )} - - -
HackNight Only
- {isStatsByHackNight ? ( - - - - {headerGroups.map(header => ( - + + + + {dataForAllEventsReport.map((event) => ( + + {event.location} + {event.totalVolunteers} + {event.totalVolunteerHours} + {event.totalVolunteerAvgHours} + ))} - - - - - {isStatsByHackNight && dataForHackNightReport.map((event) => ( - - - - - - - ))} - - {totalForHackNight && - - - {totalForHackNight.map((total, i) => ( - - ))} - - } - -
{header}
{event.location}{event.totalVolunteers}{event.totalVolunteerHours}{event.totalVolunteerAvgHours}
Total{total}
+ {totalForAllEvents && ( + + Total + {totalForAllEvents.map((total, i) => ( + {total} + ))} + + )} + + ) : ( -
No data for calculation stats
+ No data for calculation stats )} -
-
- ) : } -
+ + + HackNight Only + + {isStatsByHackNight ? ( + + + + {headerGroups.map((header) => ( + {header} + ))} + + + + {dataForHackNightReport.map((event) => ( + + {event.location} + {event.totalVolunteers} + {event.totalVolunteerHours} + {event.totalVolunteerAvgHours} + + ))} + {totalForHackNight && ( + + Total + {totalForHackNight.map((total, i) => ( + {total} + ))} + + )} + +
+ ) : ( + No data for calculation stats + )} + + + ) : ( + + )} + ); }; export default LocationTableReport; + \ No newline at end of file diff --git a/client/src/components/admin/reports/index.scss b/client/src/components/admin/reports/index.scss index 3d4a96c84..be64321c2 100644 --- a/client/src/components/admin/reports/index.scss +++ b/client/src/components/admin/reports/index.scss @@ -67,4 +67,4 @@ font-size: 13px; text-align: right; margin-top: 10px; -} +} \ No newline at end of file diff --git a/client/src/components/user-admin/EditUsers.jsx b/client/src/components/user-admin/EditUsers.jsx index 1e5f72e93..929ec9055 100644 --- a/client/src/components/user-admin/EditUsers.jsx +++ b/client/src/components/user-admin/EditUsers.jsx @@ -3,10 +3,11 @@ import '../../sass/UserAdmin.scss'; import { FormGroup, FormControlLabel, Switch } from '@mui/material' // child of UserAdmin. Displays form to update users. -const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects, updateUserActiveStatus }) => { +const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects, updateUserActiveStatus, updateUserAccessLevel }) => { const [userManagedProjects, setUserManagedProjects] = useState([]); // The projects that the selected user is assigned const [projectValue, setProjectValue] = useState(''); // State and handler for form in EditUsers const [isActive, setIsActive] = useState(userToEdit.isActive); + const [isAdmin, setIsAdmin] = useState(userToEdit.accessLevel === "admin"); // Prepare data for display const userName = `${userToEdit.name?.firstName} ${userToEdit.name?.lastName}`; @@ -64,6 +65,12 @@ const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects, updateUse updateUserActiveStatus(userToEdit, !isActive) } + const handleSetAccessLevel = () => { + const newAccessLevel = isAdmin ? "user" : "admin"; + setIsAdmin(!isAdmin); + updateUserAccessLevel(userToEdit, newAccessLevel); + }; + return (
@@ -74,15 +81,24 @@ const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects, updateUse
Email:
{userEmail}
-
-
Is Active:
-
- {isActive.toString()} +
+
Is Active:
+
+ {isActive.toString()} } onClick={() => handleSetIsActive()} />
+
+
VRSM Admin:
+
+ {isAdmin ? "Yes" : "No"} + + } onClick={() => handleSetAccessLevel()} /> + +
+
Projects:
diff --git a/client/src/pages/UserAdmin.jsx b/client/src/pages/UserAdmin.jsx index c540082ff..6271a374d 100644 --- a/client/src/pages/UserAdmin.jsx +++ b/client/src/pages/UserAdmin.jsx @@ -37,6 +37,15 @@ const UserAdmin = () => { }, [userApiService, fetchUsers] ) + // Update user's access level (admin/user) + const updateUserAccessLevel = useCallback( + async (user, newAccessLevel) => { + await userApiService.updateUserAccessLevel(user, newAccessLevel); + fetchUsers(); + }, + [userApiService, fetchUsers] + ); + const fetchProjects = useCallback(async () => { const projectRes = await projectApiService.fetchProjects(); setProjects(projectRes); @@ -65,6 +74,7 @@ const UserAdmin = () => { updateUserDb={updateUserDb} backToSearch={backToSearch} updateUserActiveStatus={updateUserActiveStatus} + updateUserAccessLevel={updateUserAccessLevel} /> ); } diff --git a/client/src/sass/UserAdmin.scss b/client/src/sass/UserAdmin.scss index 0d3759edb..adf81a32e 100644 --- a/client/src/sass/UserAdmin.scss +++ b/client/src/sass/UserAdmin.scss @@ -65,18 +65,18 @@ margin-bottom: 8px; } -.user-is-active-column-left { +.user-toggle-column-left { display: flex; flex-direction: column; flex-basis: 15%; } -.active-status { +.toggle-status { margin-right: 15px; width: 25px; } -.is-active-flex { +.toggle-flex { display: flex; align-items: center; }