diff --git a/src/Components/Accounts/Adm/admAccountsTable.js b/src/Components/Accounts/Adm/admAccountsTable.js index 82f9e0ad..3bc62f9e 100644 --- a/src/Components/Accounts/Adm/admAccountsTable.js +++ b/src/Components/Accounts/Adm/admAccountsTable.js @@ -5,12 +5,15 @@ import { toast } from 'react-toastify' import DeleteAccountPopupContent from '../../Popup/deleteAccount' import Popup from 'reactjs-popup' import cross from '../../../assets/Cross.png' -import minusButton from '../../../assets/minus-button.png' +import deleteButton from '../../../assets/deleteIcon.png' +import suspendButton from '../../../assets/suspendIcon.png' +import restoreButton from '../../../assets/restoreIcon.png' export default function AdmAccountsTable () { const [accountList, setAccountList] = useState([]) // list of accounts const [isPopupOpen, setIsPopupOpen] = useState(false) const [userId, setUserId] = useState('') + const [actionType, setActionType] = useState('delete') async function getAccountList () { const baseUrl = process.env.REACT_APP_BACKEND_URL + '/user/all' @@ -28,6 +31,27 @@ export default function AdmAccountsTable () { } else { const data = await resp.json() setAccountList(data) + getSuspendedAccountList(data) + } + } + + async function getSuspendedAccountList (list) { + const baseUrl = process.env.REACT_APP_BACKEND_URL + '/user/getDisabled' + const token = sessionStorage.getItem('token') + + const resp = await fetch(baseUrl, { + method: 'GET', + headers: { + 'x-auth-token': token, + 'Content-Type': 'application/json' + } + }) + if (resp.status === 401) { + disconnect() + } else { + const data = await resp.json() + const array = [...list, ...data] + setAccountList(array) } } @@ -39,7 +63,8 @@ export default function AdmAccountsTable () { setIsPopupOpen(!isPopupOpen) } - const callDeleteAccount = (userIdValue) => { + const callDeleteAccount = (userIdValue, action) => { + setActionType(action) setUserId(userIdValue) setIsPopupOpen(!isPopupOpen) } @@ -61,6 +86,7 @@ export default function AdmAccountsTable () { if (resp.status === 401) { disconnect() } else if (resp.status === 200) { + setIsPopupOpen(false) toast.success(deleteType ? 'Le compte a été supprimé' : 'Le compte a été suspendu') getAccountList() } else { @@ -69,13 +95,36 @@ export default function AdmAccountsTable () { } } + async function activateAccount (accountId) { + const baseUrl = process.env.REACT_APP_BACKEND_URL + '/adm/activateUser/' + accountId + const token = sessionStorage.getItem('token') + + const resp = await fetch(baseUrl, { + method: 'POST', + headers: { + 'x-auth-token': token, + 'Content-Type': 'application/json' + } + }) + if (resp.status === 401) { + disconnect() + } else if (resp.status === 200) { + setIsPopupOpen(false) + toast.success('Le compte a été restauré') + getAccountList() + } else { + toast.error("une alerte s'est produite") + getAccountList() + } + } + return (
{(close) => (
- +
)}
@@ -96,7 +145,14 @@ export default function AdmAccountsTable () { {data.firstname} {data.lastname} {data.email} - { e.stopPropagation(); callDeleteAccount(data._id) }} src={minusButton} alt='delete' title='Supprimer ou suspendre le compte' /> + + { + data.active + ? { e.stopPropagation(); callDeleteAccount(data._id, 'suspend') }} src={suspendButton} alt='delete' title='Suspendre le compte' /> + : { e.stopPropagation(); callDeleteAccount(data._id, 'restore') }} src={restoreButton} alt='delete' title='Restaurer le compte' /> + } + { e.stopPropagation(); callDeleteAccount(data._id, 'delete') }} src={deleteButton} alt='delete' title='Supprimer le compte' /> + ) } diff --git a/src/Components/Accounts/SchoolAdm/schoolAccountsTable.js b/src/Components/Accounts/SchoolAdm/schoolAccountsTable.js index d2290478..e6f33fe6 100644 --- a/src/Components/Accounts/SchoolAdm/schoolAccountsTable.js +++ b/src/Components/Accounts/SchoolAdm/schoolAccountsTable.js @@ -6,9 +6,12 @@ import { toast } from 'react-toastify' import DeleteAccountPopupContent from '../../Popup/deleteAccount' import Popup from 'reactjs-popup' import cross from '../../../assets/Cross.png' -import minusButton from '../../../assets/minus-button.png' +import deleteButton from '../../../assets/deleteIcon.png' +import suspendButton from '../../../assets/suspendIcon.png' +import restoreButton from '../../../assets/restoreIcon.png' +import Select from 'react-select' -export default function SchoolAccountsTable () { +export default function SchoolAccountsTable ({ status }) { const [teacherList, setTeacherList] = useState([]) const [studentList, setStudentList] = useState([]) const [selectedUser, setSelectedUser] = useState(null) @@ -16,13 +19,43 @@ export default function SchoolAccountsTable () { const [isPopupOpen, setIsPopupOpen] = useState(false) const [fileImage, setFileImage] = useState(null) const [userId, setUserId] = useState('') + const [isMultiStatus, setIsMultiStatus] = useState(true) + const [classesList, setClassesList] = useState([]) + const [actionType, setActionType] = useState('delete') + const [classError, setClassError] = useState(false) const [updatedUser, setUpdatedUser] = useState({ firstname: '', lastname: '', email: '', - picture: null + classes: [], + picture: null, + role: '' }) + async function getSuspendedAccountList (accounts) { + const baseUrl = process.env.REACT_APP_BACKEND_URL + '/user/getDisabled' + const token = sessionStorage.getItem('token') + + const resp = await fetch(baseUrl, { + method: 'GET', + headers: { + 'x-auth-token': token, + 'Content-Type': 'application/json' + } + }) + if (resp.status === 401) { + disconnect() + } else { + const data = await resp.json() + const array = [...accounts, ...data] + + const teacherAccounts = array.filter(account => account.role.name === 'teacher') + const studentAccounts = array.filter(account => account.role.name === 'student') + setTeacherList(teacherAccounts) + setStudentList(studentAccounts) + } + } + async function getAccountList () { const baseUrl = `${process.env.REACT_APP_BACKEND_URL}/user/all` const token = sessionStorage.getItem('token') @@ -45,7 +78,26 @@ export default function SchoolAccountsTable () { setTeacherList(teacherAccounts) setStudentList(studentAccounts) + getSuspendedAccountList(data) } + + fetch(process.env.REACT_APP_BACKEND_URL + '/shared/classes', { + method: 'GET', + headers: { + 'x-auth-token': sessionStorage.getItem('token'), + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.status === 401) { + disconnect() + } + return response.json() + }) + .then((data) => setClassesList(data)) + .catch((error) => { + toast.error(error.message) + }) } const showClasses = (classes) => { @@ -71,8 +123,11 @@ export default function SchoolAccountsTable () { firstname: user.firstname, lastname: user.lastname, email: user.email, - picture: user.picture + classes: user.classes, + picture: user.picture, + role: user.role._id }) + setIsMultiStatus(user.role.name === 'teacher') setIsEditing(true) } @@ -84,40 +139,75 @@ export default function SchoolAccountsTable () { })) } + const handleClassChange = (e) => { + setUpdatedUser(prevState => ({ + ...prevState, + classes: e + })) + } + const handleFileChange = (e) => { setFileImage(e.target.files[0]) setUpdatedUser(prevState => ({ ...prevState, picture: e.target.files[0] })) - // const selectedFile = e.target.files[0] - // if (selectedFile) { - // const reader = new FileReader() - // reader.readAsDataURL(selectedFile) - // reader.onload = () => { - // const base64Image = reader.result - // setUpdatedUser(prevState => ({ - // ...prevState, - // picture: base64Image - // })) - // } - // reader.onerror = (error) => { - // console.error('Error occurred while reading the file:', error) - // } - // } + } + + function callAction (classe, action) { + fetch(process.env.REACT_APP_BACKEND_URL + '/adm/classes/' + classe._id + action, { + method: 'PATCH', + headers: { + 'x-auth-token': sessionStorage.getItem('token'), + 'Content-Type': 'application/json' + }, + body: JSON.stringify(action === '/updateStudent' ? { studentId: selectedUser._id } : { teacherId: selectedUser._id }) + }) + .then((response) => { + if (response.status === 401) { + disconnect() + } + }) + .catch((error) => { + setClassError(true) + toast.error('Un problème est survenu avec la classe.', error.message) + }) + } + + const handleUltimateClassChange = () => { + if (selectedUser.role.name === 'teacher') { + const removeClasses = [] + const addClasses = [] + + // Assuming selectedUser and updatedUser have a `classes` property that is an array + const selectedUserClasses = selectedUser.classes || [] + const updatedUserClasses = updatedUser.classes || [] + + addClasses.push(...updatedUserClasses.filter((cls) => !selectedUserClasses.includes(cls))) + + removeClasses.push(...selectedUserClasses.filter((cls) => !updatedUserClasses.includes(cls))) + + removeClasses.map((classe) => { return callAction(classe, '/removeTeacher') }) + addClasses.map(classe => { return callAction(classe, '/addTeacher') }) + } else { + const studentClass = [] + studentClass.push(updatedUser.classes) + + studentClass.map(classe => { return callAction(classe, '/updateStudent') }) + } + if (!classError) { toast.success('Le profil a été mis à jour avec succès.') } } const handleUpdate = async (e) => { e.preventDefault() + try { const formData = new FormData() formData.append('firstname', updatedUser.firstname) formData.append('lastname', updatedUser.lastname) + formData.append('role', updatedUser.role) formData.append('email', updatedUser.email) - // if (updatedUser.picture) { - // formData.append('file', updatedUser.picture) - // } - console.log(fileImage) + if (fileImage) { formData.append('file', fileImage) } @@ -133,17 +223,11 @@ export default function SchoolAccountsTable () { if (response.status === 401) { disconnect() } else if (response.ok) { - // setSelectedUser(null) - // setUpdatedUser({ - // firstname: '', - // lastname: '', - // email: '', - // picture: null - // }) - setFileImage(null) - toast.success('Le profil a été mis à jour avec succès.') setIsEditing(false) + setFileImage(null) + handleUltimateClassChange() getAccountList() // Refresh the list + setClassError(false) } else { toast.error('Erreur lors de la mise à jour du profil: ' + response.statusText) } @@ -171,6 +255,30 @@ export default function SchoolAccountsTable () { } else if (resp.status === 200) { toast.success(deleteType ? 'Le compte a été supprimé' : 'Le compte a été suspendu') getAccountList() + setIsPopupOpen(false) + } else { + toast.error("une alerte s'est produite") + getAccountList() + } + } + + async function activateAccount (accountId) { + const baseUrl = process.env.REACT_APP_BACKEND_URL + '/adm/activateUser/' + accountId + const token = sessionStorage.getItem('token') + + const resp = await fetch(baseUrl, { + method: 'POST', + headers: { + 'x-auth-token': token, + 'Content-Type': 'application/json' + } + }) + if (resp.status === 401) { + disconnect() + } else if (resp.status === 200) { + toast.success('Le compte a été restauré') + getAccountList() + setIsPopupOpen(false) } else { toast.error("une alerte s'est produite") getAccountList() @@ -186,7 +294,8 @@ export default function SchoolAccountsTable () { setIsEditing(!isEditing) } - const callDeleteAccount = (userIdValue) => { + const callDeleteAccount = (userIdValue, action) => { + setActionType(action) setUserId(userIdValue) setIsPopupOpen(!isPopupOpen) } @@ -197,7 +306,7 @@ export default function SchoolAccountsTable () { {(close) => (
- +
)} @@ -207,44 +316,63 @@ export default function SchoolAccountsTable () {

Modifier Profil

-
+ +
+ +
- - +
- - +
- - +
@@ -266,8 +394,8 @@ export default function SchoolAccountsTable () { Nom Email Classe(s) - Modifier - {sessionStorage.getItem('role') !== 'teacher' ? : ''} + {status && Modifier} + {status ? : ''} @@ -279,8 +407,16 @@ export default function SchoolAccountsTable () { {data.lastname} {data.email} {showClasses(data.classes)} - - {sessionStorage.getItem('role') !== 'teacher' && { e.stopPropagation(); callDeleteAccount(data._id) }} src={minusButton} alt='delete' title='Supprimer ou suspendre le compte' />} + {status && } + {status && + + { + data.active + ? { e.stopPropagation(); callDeleteAccount(data._id, 'suspend') }} src={suspendButton} alt='delete' title='Suspendre le compte' /> + : { e.stopPropagation(); callDeleteAccount(data._id, 'restore') }} src={restoreButton} alt='delete' title='Restaurer le compte' /> + } + { e.stopPropagation(); callDeleteAccount(data._id, 'delete') }} src={deleteButton} alt='delete' title='Supprimer le compte' /> + } ) } @@ -302,8 +438,8 @@ export default function SchoolAccountsTable () { Nom Email Classe - Modifier - {sessionStorage.getItem('role') !== 'teacher' ? : ''} + {status && Modifier} + {status ? : ''} @@ -315,8 +451,16 @@ export default function SchoolAccountsTable () { {data.lastname} {data.email} {showClasses(data.classes)} - - {sessionStorage.getItem('role') !== 'teacher' && { e.stopPropagation(); callDeleteAccount(data._id) }} src={minusButton} alt='delete' title='Supprimer ou suspendre le compte' />} + {status && } + {status && + + { + data.active + ? { e.stopPropagation(); callDeleteAccount(data._id, 'suspend') }} src={suspendButton} alt='delete' title='Suspendre le compte' /> + : { e.stopPropagation(); callDeleteAccount(data._id, 'restore') }} src={restoreButton} alt='delete' title='Restaurer le compte' /> + } + { e.stopPropagation(); callDeleteAccount(data._id, 'delete') }} src={deleteButton} alt='delete' title='Supprimer le compte' /> + } ) } diff --git a/src/Components/Aides/aides.jsx b/src/Components/Aides/aides.jsx index 041565f1..46f9b6e6 100644 --- a/src/Components/Aides/aides.jsx +++ b/src/Components/Aides/aides.jsx @@ -3,13 +3,13 @@ import '../../css/Components/Aides/aides.scss' import { disconnect } from '../../functions/disconnect' import phoneIcon from '../../assets/phoneIcon.png' import mailIcon from '../../assets/mailIcon.png' +import { toast } from 'react-toastify' export default function AidePage () { const [categories, setCategories] = useState([]) const [contacts, setContacts] = useState([]) const [chosenContact, setChosenContact] = useState([]) const [filteredContacts, setFilteredContacts] = useState([]) - const [errMessage, setErrMessage] = useState('') const [defaultID, setDefaultID] = useState(null) const [selectedCat, setSelectedCat] = useState(null) const [selectedContact, setSelectedContact] = useState(null) @@ -39,7 +39,7 @@ export default function AidePage () { setSelectedCat(filterID[0]._id) } }) - .catch(error => setErrMessage(error.message)) + .catch(error => toast.error(error.message)) fetch(helpNumbersUrl, { method: 'GET', @@ -59,7 +59,7 @@ export default function AidePage () { setChosenContact(data[0]) setSelectedContact(data[0]._id) }) - .catch(error => setErrMessage('Erreur ' + error.status + ': ' + error.message)) + .catch(error => toast.error('Erreur ' + error.status + ': ' + error.message)) }, []) const filterContactsByCategory = (category) => { @@ -90,7 +90,6 @@ export default function AidePage () { return (
-

{errMessage || ''}

{categories.map((category) => (
- {isCategoryEmpty ? ( // Check if the category is empty -

Aucun numéro disponible dans cette catégorie.

- ) : ( - <> -

{chosenContact.name}

-

{chosenContact.description}

- {chosenContact.telephone && ( -
- Telephone -

{chosenContact.telephone}

-
+ {isCategoryEmpty + ? (

Aucun numéro disponible dans cette catégorie.

) + : ( + <> +

{chosenContact.name}

+

{chosenContact.description}

+ {chosenContact.telephone && ( +
+ Telephone +

{chosenContact.telephone}

+
+ )} + {chosenContact.email && ( +
+ Adresse Email +

{chosenContact.email}

+
+ )} + )} - {chosenContact.email && ( -
- Adresse Email -

{chosenContact.email}

-
- )} - - )}
diff --git a/src/Components/Alerts/lastAlerts.js b/src/Components/Alerts/lastAlerts.js index 09e59fcb..08801d96 100644 --- a/src/Components/Alerts/lastAlerts.js +++ b/src/Components/Alerts/lastAlerts.js @@ -5,9 +5,9 @@ import { Link } from 'react-router-dom' import rightArrowInverted from '../../assets/right-arrow-inverted.png' import UserProfile from '../userProfile/userProfile' import { disconnect } from '../../functions/disconnect' +import { toast } from 'react-toastify' export function LastAlerts () { - const [errMessage, setErrMessage] = useState('') const [alerts, setAlerts] = useState([]) useEffect(() => { @@ -75,7 +75,7 @@ export function LastAlerts () { promisedList.then((alertList) => setAlerts(alertList)) }) .catch((error) => { - setErrMessage('Erreur : ', error.message) + toast.error('Erreur : ', error.message) }) }, []) @@ -117,9 +117,6 @@ export function LastAlerts () { ) : (

Vous n'avez pas de nouvelle alerte.

) } - { - errMessage ?

{errMessage}

: '' - }
) diff --git a/src/Components/Alerts/showAlerts.js b/src/Components/Alerts/showAlerts.js index 5325fd25..82f768f3 100644 --- a/src/Components/Alerts/showAlerts.js +++ b/src/Components/Alerts/showAlerts.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React from 'react' import '../../css/Components/Alerts/lastAlerts.scss' import '../../css/pages/createAlerts.scss' import UserProfile from '../userProfile/userProfile' diff --git a/src/Components/ChatRoom/chatRoom.jsx b/src/Components/ChatRoom/chatRoom.jsx index 10086f7c..87059561 100644 --- a/src/Components/ChatRoom/chatRoom.jsx +++ b/src/Components/ChatRoom/chatRoom.jsx @@ -15,7 +15,6 @@ import UserProfile from '../../Components/userProfile/userProfile' const Messages = () => { const [conversations, setConversations] = useState([]) const [currentConversation, setCurrentConversation] = useState('') - const [currentParticipants, setCurrentParticipants] = useState('') const [notification, setNotification] = useState({ visible: false, message: '', type: '' }) const { send, chats } = useContext(WebsocketContext) // eslint-disable-line const inputFile = useRef(null) @@ -51,7 +50,7 @@ const Messages = () => { noUserParticipants.map((participant) => ( convName.push(participant.firstname + ' ' + participant.lastname) )) - setCurrentParticipants(convName.join(', ')) + // setCurrentParticipants(convName.join(', ')) return { _id: conversation._id, date: conversation.date, @@ -265,6 +264,10 @@ const Messages = () => { setShowCreateConversationPopup(!showCreateConversationPopup) } + const truncateString = (str) => { + return str.length > 25 ? str.slice(0, 30) + '...' : str + } + const createConversation = async (convTitle, selectedContacts) => { try { const userId = localStorage.getItem('id') @@ -397,7 +400,6 @@ const Messages = () => { conversations={conversations} currentConversation={currentConversation} setCurrentConversation={setCurrentConversation} - setCurrentParticipants={setCurrentParticipants} clearMessageAndError={clearMessageAndError} openCreateConversationPopup={openCreateConversationPopup} /> @@ -411,7 +413,7 @@ const Messages = () => {
-
{currentConversation.name}
+
{truncateString(currentConversation.name)}
{currentConversation.participants.length} {currentConversation.participants.length > 1 ? 'membres' : 'membre'}
@@ -420,15 +422,15 @@ const Messages = () => { - Signaler} modal contentStyle={{width: '400px'}}> - {(close) => ( -
- -
- )} + Signaler} modal contentStyle={{ width: '400px' }}> + {(close) => ( +
+ +
+ )}
@@ -493,7 +495,7 @@ const Messages = () => {
Aucune conversation sélectionnée.
)}
- + {(close) => (
Nouvelle conversation @@ -502,7 +504,7 @@ const Messages = () => {
)}
- setShowAddParticipantsPopup(false)} modal contentStyle={{width: '400px'}}> + setShowAddParticipantsPopup(false)} modal contentStyle={{ width: '400px' }}> {(close) => (
Gestion de la conversation diff --git a/src/Components/ChatRoom/chatRoomSidebar.jsx b/src/Components/ChatRoom/chatRoomSidebar.jsx index 352697cc..3874acab 100644 --- a/src/Components/ChatRoom/chatRoomSidebar.jsx +++ b/src/Components/ChatRoom/chatRoomSidebar.jsx @@ -6,7 +6,6 @@ const ChatRoomSidebar = ({ conversations, currentConversation, setCurrentConversation, - setCurrentParticipants, clearMessageAndError, openCreateConversationPopup }) => { @@ -23,7 +22,10 @@ const ChatRoomSidebar = ({ conversation.participants?.map((participant) => ( conv.push(participant.firstname + ' ' + participant.lastname) )) - setCurrentParticipants(conv.join(', ')) + } + + const truncateString = (str) => { + return str.length > 25 ? str.slice(0, 30) + '...' : str } return ( @@ -33,13 +35,12 @@ const ChatRoomSidebar = ({ cross + Nouvelle conversation - {/* */}
{conversations.map((conversation, index) => (
handleClick(conversation)}> -
- {conversation.name} +
+ {truncateString(conversation.name)}
{ (chats.value.unseenChats.includes(conversation._id)) && diff --git a/src/Components/ChatRoom/reportButton.jsx b/src/Components/ChatRoom/reportButton.jsx index 28947a90..482a0906 100644 --- a/src/Components/ChatRoom/reportButton.jsx +++ b/src/Components/ChatRoom/reportButton.jsx @@ -2,8 +2,8 @@ import React, { useState } from 'react' import '../../css/pages/createReports.scss' import '../../css/Components/Popup/popup.scss' import { disconnect } from '../../functions/disconnect' -import Select from 'react-select'; -import { toast } from 'react-toastify'; +import Select from 'react-select' +import { toast } from 'react-toastify' import cross from '../../assets/Cross.png' const ReportButton = ({ currentConversation, close }) => { @@ -30,19 +30,20 @@ const ReportButton = ({ currentConversation, close }) => { const colourStyles = { control: (styles) => ( - { ...styles, + { + ...styles, backgroundColor: 'white', - height: '45px', + height: '45px' } ), multiValueLabel: (styles, { data }) => ({ ...styles, fontWeight: '500' - }), - }; + }) + } const handleCloseBtn = () => { - close(); + close() } const handleConfirmClick = async () => { @@ -85,7 +86,7 @@ const ReportButton = ({ currentConversation, close }) => { return ( <> -
+
Créer un Signalement
@@ -115,9 +116,9 @@ const ReportButton = ({ currentConversation, close }) => { getOptionLabel={(option) => (option.firstname + ' ' + option.lastname)} /> -