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 &&
-
-
+
-
+
-
handleCalculateStatsBtn(event)}
- >
+ handleCalculateStatsBtn(event)}>
Calculate Stats
-
-
+
+
}
-
-
- 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 => (
- {header} |
- ))}
-
-
-
-
- {dataForAllEventsReport.map((event) => (
-
- {event.location} |
- {event.totalVolunteers} |
- {event.totalVolunteerHours} |
- {event.totalVolunteerAvgHours} |
-
- ))}
-
- {totalForAllEvents &&
-
- Total |
- {totalForAllEvents.map((total, i) => (
- {total} |
+
+
+
+ {headerGroups.map((header) => (
+ {header}
))}
-
- }
-
-
- ) : (
- No data for calculation stats
- )}
-
-
- HackNight Only
- {isStatsByHackNight ? (
-
-
-
- {headerGroups.map(header => (
- {header} |
+
+
+
+ {dataForAllEventsReport.map((event) => (
+
+ {event.location}
+ {event.totalVolunteers}
+ {event.totalVolunteerHours}
+ {event.totalVolunteerAvgHours}
+
))}
-
-
-
-
- {isStatsByHackNight && dataForHackNightReport.map((event) => (
-
- {event.location} |
- {event.totalVolunteers} |
- {event.totalVolunteerHours} |
- {event.totalVolunteerAvgHours} |
-
- ))}
-
- {totalForHackNight &&
-
- Total |
- {totalForHackNight.map((total, i) => (
- {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;
}