From f5a69154810553f04c367b01205e2c9740d7da4d Mon Sep 17 00:00:00 2001 From: Imran Issa Date: Sat, 20 Apr 2024 10:30:39 +0200 Subject: [PATCH 01/97] added confirm dialog below visibility toggle --- src/actions/userManagement.js | 18 +++++++ .../PermissionsManagement/PermissionsConst.js | 6 +++ .../UserProfile/TeamsAndProjects/Switch.css | 51 +++++++++++++++++++ .../UserProfile/TeamsAndProjects/Switch.jsx | 25 +++++++++ .../TeamsAndProjects/UserTeamsTable.jsx | 31 +++++------ src/components/UserProfile/UserProfile.jsx | 42 +++++++++++---- src/utils/URL.js | 1 + 7 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 src/components/UserProfile/TeamsAndProjects/Switch.css create mode 100644 src/components/UserProfile/TeamsAndProjects/Switch.jsx diff --git a/src/actions/userManagement.js b/src/actions/userManagement.js index 46036d3524..74ec943ed3 100644 --- a/src/actions/userManagement.js +++ b/src/actions/userManagement.js @@ -72,6 +72,24 @@ export const updateRehireableStatus = (user, isRehireable) => { }; }; +/** + * Switches the visibility of a user + * @param{*} user - the user whose visibility is to be changed + * @param{boolean} isVisible - the new visiblity status + */ +export const toggleVisibility = (user, isVisible) => { + const userProfile = { ...user }; + userProfile.isVisible = isVisible + const requestData = { isVisible }; + + const toggleVisibilityPromise = axios.patch(ENDPOINTS.TOGGLE_VISIBILITY(user._id), requestData) + return async dispatch => { + toggleVisibilityPromise.then(res => { + dispatch(userProfileUpdateAction(userProfile)); + }); + }; +}; + /** * delete an existing user * @param {*} user - the user to be deleted diff --git a/src/components/PermissionsManagement/PermissionsConst.js b/src/components/PermissionsManagement/PermissionsConst.js index a4858e558a..c0f8fa3d68 100644 --- a/src/components/PermissionsManagement/PermissionsConst.js +++ b/src/components/PermissionsManagement/PermissionsConst.js @@ -74,6 +74,12 @@ export const permissionLabels = [ description: 'Gives the user permission to change the status of any user on the user profile page or User Management Page. "User Profile" -> "Green round button"', }, + { + label: 'Toggle Invisibility Permission Self and Others', + key: 'toggleInvisibility', + description: + 'Gives the user permission to change the invisibility toggle for themselves and others', + }, { label: 'Handle Blue Squares', key: 'infringementAuthorizer', diff --git a/src/components/UserProfile/TeamsAndProjects/Switch.css b/src/components/UserProfile/TeamsAndProjects/Switch.css new file mode 100644 index 0000000000..bddb036cc1 --- /dev/null +++ b/src/components/UserProfile/TeamsAndProjects/Switch.css @@ -0,0 +1,51 @@ +.switch-container { + display: flex; + flex-direction: row; + justify-content: center; +} + +.switch-checkbox { + height: 0; + width: 0; + visibility: hidden; +} + +.switch-title { + margin: 0 5px; + font-weight: bold; +} + +.switch-label { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + height: 25px; + width: 40px; + background: darkslategrey; + border-radius: 100px; + position: relative; + transition: background-color .2s; +} + +.switch-label .switch-button { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + border-radius: 50%; + transition: 0.2s; + background: #fff; + box-shadow: 0 0 2px 0 rgba(10, 10, 10, 0.29); +} + +.switch-checkbox:checked + .switch-label .switch-button { + left: calc(100% - 2px); + transform: translateX(-100%); +} + +.switch-label:active .switch-button { + width: 20px; +} \ No newline at end of file diff --git a/src/components/UserProfile/TeamsAndProjects/Switch.jsx b/src/components/UserProfile/TeamsAndProjects/Switch.jsx new file mode 100644 index 0000000000..1415f808e6 --- /dev/null +++ b/src/components/UserProfile/TeamsAndProjects/Switch.jsx @@ -0,0 +1,25 @@ +import "./Switch.css" +const Switch = ({ isOn, handleToggle}) => { + return ( +
+

invisible

+ + +

visible

+
+ ); +} + +export default Switch \ No newline at end of file diff --git a/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx b/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx index 1e305cbf5e..3a1500741e 100644 --- a/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx +++ b/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx @@ -1,11 +1,12 @@ import { React, useState } from 'react'; import { Button, Input, Col, Tooltip } from 'reactstrap'; -import './TeamsAndProjects.css'; -import ToggleSwitch from '../UserProfileEdit/ToggleSwitch'; import hasPermission from '../../../utils/permissions'; -import styles from './UserTeamsTable.css'; -import { boxStyle } from 'styles'; import { connect } from 'react-redux'; +import { boxStyle } from 'styles'; +import Switch from './Switch'; +import './TeamsAndProjects.css'; +import './UserTeamsTable.css'; + const UserTeamsTable = props => { const [tooltipOpen, setTooltip] = useState(false); @@ -37,17 +38,17 @@ const UserTeamsTable = props => {
- {props.canEditVisibility || ['Owner', 'Administrator'].includes(props.role) && ( + {props.canEditVisibility && (
Visibility - +
)} @@ -157,11 +158,11 @@ const UserTeamsTable = props => { Visibility - + )} diff --git a/src/components/UserProfile/UserProfile.jsx b/src/components/UserProfile/UserProfile.jsx index 2ba679ca5d..1cd034d1c0 100644 --- a/src/components/UserProfile/UserProfile.jsx +++ b/src/components/UserProfile/UserProfile.jsx @@ -44,7 +44,7 @@ import ResetPasswordButton from '../UserManagement/ResetPasswordButton'; import Badges from './Badges'; import TimeEntryEditHistory from './TimeEntryEditHistory'; import ActiveInactiveConfirmationPopup from '../UserManagement/ActiveInactiveConfirmationPopup'; -import { updateUserStatus, updateRehireableStatus } from '../../actions/userManagement'; +import { updateUserStatus, updateRehireableStatus, toggleVisibility } from '../../actions/userManagement'; import { UserStatus } from '../../utils/enums'; import BlueSquareLayout from './BlueSquareLayout'; import TeamWeeklySummaries from './TeamWeeklySummaries/TeamWeeklySummaries'; @@ -102,6 +102,7 @@ function UserProfile(props) { const [isTeamSaved, setIsTeamSaved] = useState(false); const [summaryIntro, setSummaryIntro] = useState(''); const [showConfirmDialog, setShowConfirmDialog] = useState(false); + const [showToggleVisibilityModal, setShowToggleVisibilityModal] = useState(false); const [pendingRehireableStatus, setPendingRehireableStatus] = useState(null); const [isRehireable, setIsRehireable] = useState(null); @@ -310,7 +311,6 @@ function UserProfile(props) { const newUserProfile = response.data; // Assuming newUserProfile contains isRehireable attribute setIsRehireable(newUserProfile.isRehireable); // Update isRehireable based on fetched data - newUserProfile.totalIntangibleHrs = Number(newUserProfile.totalIntangibleHrs.toFixed(2)); const teamId = newUserProfile?.teams[0]?._id; @@ -583,7 +583,7 @@ function UserProfile(props) { const activeInactivePopupClose = () => { setActiveInactivePopupOpen(false); - }; + }; const handleRehireableChange = () => { const newRehireableStatus = !isRehireable; @@ -665,12 +665,25 @@ function UserProfile(props) { }; const onUserVisibilitySwitch = () => { - setUserProfile({ + setShowToggleVisibilityModal(true); + } + + const handleVisibilityChange = () => { + setShowToggleVisibilityModal(false); + const visibility = !userProfile.isVisible; + const newUserProfile = { ...userProfile, - isVisible: !userProfile.isVisible ?? true, - }); + isVisible: visibility + }; + toggleVisibility(newUserProfile, visibility); + setUserProfile(newUserProfile); + setOriginalUserProfile(newUserProfile); }; + const handleCloseConfirmVisibilityModal = () =>{ + setShowToggleVisibilityModal(false) + } + if ((showLoading && !props.isAddNewUser) || userProfile === undefined) { return ( @@ -696,6 +709,8 @@ function UserProfile(props) { const canUpdatePassword = props.hasPermission('updatePassword'); const canGetProjectMembers = props.hasPermission('getProjectMembers'); const canChangeRehireableStatus = props.hasPermission('changeUserRehireableStatus') + const canEditVisibility = props.hasPermission('toggleInvisibility'); + const targetIsDevAdminUneditable = cantUpdateDevAdminDetails(userProfile.email, authEmail); @@ -733,7 +748,6 @@ function UserProfile(props) { const handleEndDate = async endDate => { setUserEndDate(endDate); }; - return (
)} + + Confirm Status Change + + {`Are you sure you want to change the user status to ${userProfile.isVisible ? 'Invisible' : 'Visible'}?`} + + + {' '} + + + @@ -1041,7 +1065,7 @@ function UserProfile(props) { role={requestorRole} onUserVisibilitySwitch={onUserVisibilitySwitch} isVisible={userProfile.isVisible} - canEditVisibility={canEdit && !['Volunteer', 'Mentor'].includes(userProfile.role)} + canEditVisibility={canEditVisibility} handleSubmit={handleSubmit} disabled={ !formValid.firstName || @@ -1263,7 +1287,7 @@ function UserProfile(props) { role={requestorRole} onUserVisibilitySwitch={onUserVisibilitySwitch} isVisible={userProfile.isVisible} - canEditVisibility={canEdit && userProfile.role != 'Volunteer'} + canEditVisibility={canEditVisibility} handleSubmit={handleSubmit} disabled={ !formValid.firstName || diff --git a/src/utils/URL.js b/src/utils/URL.js index 0718b33b22..8c0ab7b43e 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -7,6 +7,7 @@ export const ENDPOINTS = { USER_PROFILE_PROPERTY: userId => `${APIEndpoint}/userprofile/${userId}/property`, USER_PROFILES: `${APIEndpoint}/userprofile/`, UPDATE_REHIREABLE_STATUS: userId => `${APIEndpoint}/userprofile/${userId}/rehireable`, + TOGGLE_VISIBILITY: userId => `${APIEndpoint}/userprofile/${userId}/toggleInvisibility`, INFO_COLLECTIONS: `${APIEndpoint}/informations`, INFO_COLLECTION: infoId => `${APIEndpoint}/informations/${infoId}`, From a76870244c6fa8d245a19a30a8df3ac9bdf03841 Mon Sep 17 00:00:00 2001 From: Imran Issa Date: Sat, 20 Apr 2024 12:08:53 +0200 Subject: [PATCH 02/97] added confirm dialog below visibility toggle --- src/components/UserProfile/UserProfile.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/UserProfile/UserProfile.jsx b/src/components/UserProfile/UserProfile.jsx index 1cd034d1c0..2d659ca45a 100644 --- a/src/components/UserProfile/UserProfile.jsx +++ b/src/components/UserProfile/UserProfile.jsx @@ -773,9 +773,9 @@ function UserProfile(props) { /> )} - Confirm Status Change + Confirm Visibility Change - {`Are you sure you want to change the user status to ${userProfile.isVisible ? 'Invisible' : 'Visible'}?`} + {`Are you sure you want to change the user visibility to ${userProfile.isVisible ? 'Invisible' : 'Visible'}?`} {' '} From df62d0cc4c3bf6ea202000ce4a133ca4908ebcd8 Mon Sep 17 00:00:00 2001 From: Imran Issa <87269725+ImzIssa@users.noreply.github.com> Date: Fri, 10 May 2024 16:37:23 +0200 Subject: [PATCH 03/97] Update src/actions/userManagement.js Co-authored-by: Mohamed Sharif <135287156+mSharifHub@users.noreply.github.com> --- src/actions/userManagement.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/actions/userManagement.js b/src/actions/userManagement.js index 74ec943ed3..5fd87fc5ff 100644 --- a/src/actions/userManagement.js +++ b/src/actions/userManagement.js @@ -84,7 +84,12 @@ export const toggleVisibility = (user, isVisible) => { const toggleVisibilityPromise = axios.patch(ENDPOINTS.TOGGLE_VISIBILITY(user._id), requestData) return async dispatch => { - toggleVisibilityPromise.then(res => { + .catch((err)=>{ + console.error("failed to toggle visibility:',error): + dispatch(userProfileErrorAction(error)); + } + } + ........ dispatch(userProfileUpdateAction(userProfile)); }); }; From d51d5e1d4aad4f1191a13b4e5adacd7d2590afb7 Mon Sep 17 00:00:00 2001 From: Imran Issa Date: Wed, 15 May 2024 06:05:55 +0200 Subject: [PATCH 04/97] added catch in toggleVisibilityPromise request --- src/actions/userManagement.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/actions/userManagement.js b/src/actions/userManagement.js index 5fd87fc5ff..09b2e30890 100644 --- a/src/actions/userManagement.js +++ b/src/actions/userManagement.js @@ -84,15 +84,13 @@ export const toggleVisibility = (user, isVisible) => { const toggleVisibilityPromise = axios.patch(ENDPOINTS.TOGGLE_VISIBILITY(user._id), requestData) return async dispatch => { - .catch((err)=>{ - console.error("failed to toggle visibility:',error): - dispatch(userProfileErrorAction(error)); - } - } - ........ + toggleVisibilityPromise.then(res => { dispatch(userProfileUpdateAction(userProfile)); + }).catch(err => { + console.error("failed to toggle visibility: ", err); }); }; + }; /** From 82690d1db9aaec0a05fc54777f1cf26adb2693c9 Mon Sep 17 00:00:00 2001 From: luisarevalo21 Date: Mon, 3 Jun 2024 20:24:11 -0700 Subject: [PATCH 05/97] restored tracker modal --- .../Warnings/modals/WarningTrackerModal.jsx | 382 ++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 src/components/Warnings/modals/WarningTrackerModal.jsx diff --git a/src/components/Warnings/modals/WarningTrackerModal.jsx b/src/components/Warnings/modals/WarningTrackerModal.jsx new file mode 100644 index 0000000000..6529f0c523 --- /dev/null +++ b/src/components/Warnings/modals/WarningTrackerModal.jsx @@ -0,0 +1,382 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable no-unused-vars */ +/* eslint-disable spaced-comment */ +/* eslint-disable no-undef */ +/* eslint-disable react/no-unescaped-entities */ +/* eslint-disable react/destructuring-assignment */ +/* eslint-disable no-alert */ +import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Alert } from 'reactstrap'; +import { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Popover from 'react-bootstrap/Popover'; +import { + postNewWarning, + getWarningDescriptions, + updateWarningDescription, + deleteWarningDescription, + editWarningDescription, +} from '../../../actions/warnings'; + +import reorder from '../reorder.svg'; + +/** + * + * + * Modal displaying information about how time entry works + * @param {*} props + * @param {Boolean} props.visible + * @param {Func} props.setVisible + */ +function WarningTrackerModal({ + toggleWarningTrackerModal, + getUsersWarnings, + setToggleWarningTrackerModal, +}) { + const [toggeleWarningInput, setToggeleWarningInput] = useState(false); + const [newWarning, setNewWarning] = useState(''); + const [warningDescriptions, setWarningDescriptions] = useState([]); + const [toggleDeleteModal, setToggleDeleteModal] = useState(false); + const [warningToDelete, setWarningToDelete] = useState(null); + const [warningEdited, setWarningEdited] = useState(false); + const [editedWarning, setEditedWarning] = useState(null); + const [warningWasEdited, setWarningWasEdited] = useState(false); + + const [error, setError] = useState(null); + const dispatch = useDispatch(); + + const fetchWarningDescriptions = async () => { + dispatch(getWarningDescriptions()).then(res => { + if (res.error) { + setError(res.error); + return; + } + setWarningDescriptions(res); + }); + }; + useEffect(() => { + fetchWarningDescriptions(); + }, []); + + useEffect(() => { + setTimeout(() => { + setWarningWasEdited(false); + }, 5000); + }, [warningWasEdited]); + + const handleOverlayTrigger = title => { + if (title === 'info') { + return ( + + Information + +

+ Pressing the "+" button will allow you to activate the warning tracker. +

+

+ Pressing the "-" button will allow you to deactivate the warning tracker. +

+

+ Pressing the "x" button will allow you to delete the warning tracker. This will also + delete all assoicated warnings for every user (be careful doing this!). +

+

+ Pressing the "Add New Warning Tracker" button will allow you to add a new warning to + the list. +

+
+
+ ); + } + return ( + + Description + + This will {title} this warning tracker,{' '} + {title === 'activate' + ? 'showing all saved warning-tracking data of this type' + : 'retaining the data but hiding all warning tracking of this type'} + + + ); + }; + + const handleTriggerDeleteWarningDescription = warning => { + setToggleDeleteModal(true); + setWarningToDelete(warning); + }; + + const handleDeactivate = warningId => { + dispatch(updateWarningDescription(warningId)).then(res => { + if (res.error) { + setError(res.error); + return; + } + setWarningDescriptions(prev => + prev.map(warning => { + if (warning._id === warningId) { + return { ...warning, activeWarning: !warning.activeWarning }; + } + return warning; + }), + ); + getUsersWarnings(); + }); + }; + const handleDeleteWarningDescription = warningId => { + dispatch(deleteWarningDescription(warningId)).then(res => { + if (res.error) { + setError(res.error); + return; + } + setWarningDescriptions(prev => prev.filter(warning => warning._id !== warningId)); + getUsersWarnings(); + }); + }; + + // const handleChange = e => { + // const value = e.target.value; + // setNewWarning(value); + // }; + + const handleEditWarningDescription = (e, warningId) => { + setWarningEdited(true); + + const updatedWarningDescriptions = warningDescriptions.map(warning => { + if (warning._id === warningId) { + const updatedWarning = { ...warning, warningTitle: e.target.value }; + setEditedWarning(updatedWarning); + return updatedWarning; + } + return { ...warning, disabled: true }; + }); + setWarningDescriptions(updatedWarningDescriptions); + }; + + const handleCancelEdit = () => { + fetchWarningDescriptions(); + setWarningEdited(false); + setWarningWasEdited(false); + }; + + const handleSaveEditedWarning = () => { + dispatch(editWarningDescription(editedWarning)).then(res => { + if (res.error) { + setError(res.error); + return; + } + setWarningEdited(false); + setEditedWarning(null); + getUsersWarnings(); + setError(null); + fetchWarningDescriptions(); + setWarningWasEdited(true); + }); + }; + // eslint-disable-next-line no-shadow + const handleAddNewWarning = (e, newWarning) => { + e.preventDefault(); + + if (newWarning === '') return; + const trimmedWarning = newWarning.trim(); + dispatch(postNewWarning({ newWarning: trimmedWarning, activeWarning: true })).then(res => { + setNewWarning(''); + if (res?.error) { + setError(res.error); + return; + } + if (res?.message) { + setError(res.message); + return; + } + setWarningDescriptions(res.newWarnings); + getUsersWarnings(); + setError(null); + }); + }; + + if (toggleDeleteModal) { + const { warningTitle, _id } = warningToDelete; + + return ( + setToggleDeleteModal(false)}> + +

Whooooo Tiger!!

+

Are you sure you want to delete this warning?

+

Deleteing this warning will delete all associated data tied to it from all users.

+

Warning Title: {warningTitle}

+
+ + + + + + +
+ ); + } + + return ( + setToggleWarningTrackerModal(false)}> + + Current Warning Descriptions + +