1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'usergroups' into fix-requests

This commit is contained in:
SystemsPurge 2025-01-06 14:03:45 +01:00
commit 6098e87077
No known key found for this signature in database
4 changed files with 695 additions and 490 deletions

View file

@ -15,103 +15,210 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { useState } from 'react';
import { Form, Col, Button} from 'react-bootstrap';
import Dialog from '../../../common/dialogs/dialog';
import { useGetScenariosQuery } from '../../../store/apiSlice';
import React, { useEffect } from "react";
import { useState } from "react";
import { Form, Col, Button, Card } from "react-bootstrap";
import Dialog from "../../../common/dialogs/dialog";
import { useGetScenariosQuery } from "../../../store/apiSlice";
const AddUsergroupDialog = ({isModalOpened, onClose}) => {
const AddUsergroupDialog = ({ isModalOpened, onClose }) => {
const [name, setName] = useState("");
const [isValid, setIsValid] = useState(true);
//each mapping is a 'scenarioID' and a bool value 'duplicate' that indicates wether this scenario is duplicated for each user in the group
const [scenarioMappings, setScenarioMappings] = useState([]);
const [name, setName] = useState('');
const [isValid, setIsValid] = useState(false);
const [selectedOption, setSelectedOption] = useState('addUsersToScenario');
const [selectedScenarioID, setSelectedScenarioID] = useState('');
const { data: { scenarios } = {}, isLoading: isLoadingScenarios } =
useGetScenariosQuery();
const {data: {scenarios} = {}, isLoading: isLoadingScenarios} = useGetScenariosQuery();
const checkValidity = (newName, newMappings) => {
//group names have to be at lest 3 characters long and cannot start with spaces
const isNameValid = newName.length >= 3 && !/^\s/.test(newName);
//scenario ID is chosen from the dropdown therefore we just make sure that empty option is not selected
const areMappingsValid = newMappings.every(
(mapping) => mapping.scenarioID != ""
);
const checkValidity = (name, scenarioID) => {
//group names have to be at lest 3 characters long and cannot start with spaces
const isNameValid = name.length >= 3 && !(/^\s/.test(name));
//scenario ID is chosen from the dropdown therefore we just make sure that empty option is not selected
const isScenarioIDValid = scenarioID !== '';
setIsValid(isNameValid && isScenarioIDValid);
setIsValid(isNameValid && areMappingsValid);
//this is only for the check before the submition
return isNameValid && areMappingsValid;
};
const handleNameChange = (e) => {
const newName = e.target.value;
setName(newName);
if (!isValid) checkValidity(newName, scenarioMappings);
};
const handleRadioChange = (index, value) => {
const updatedMappings = [...scenarioMappings];
updatedMappings[index].duplicate = value === "duplicateScenarioForUsers";
setScenarioMappings(updatedMappings);
};
const handleClose = (canceled) => {
if (canceled) {
onClose(null);
} else {
if (checkValidity(name, scenarioMappings)) {
const scenarioMappingsModified = [...scenarioMappings].map(
(mapping) => ({
...mapping,
scenarioID: Number(mapping.scenarioID),
})
);
onClose({
name: name.trim(),
scenarioMappings: scenarioMappingsModified,
});
}
}
};
const handleNameChange = (e) => {
const newName = e.target.value;
setName(newName);
checkValidity(newName, selectedScenarioID);
}
const handleReset = () => {
setName("");
setScenarioMappings([]);
};
const handleRadioChange = (e) => {
setSelectedOption(e.target.value);
}
const handleSelectChange = (index, value) => {
const updatedMappings = [...scenarioMappings];
updatedMappings[index].scenarioID = value;
setScenarioMappings(updatedMappings);
if (!isValid) checkValidity(name, updatedMappings);
};
const handleClose = (canceled) => {
if(canceled) {
onClose(null);
} else {
onClose({name: name, scenarioID: selectedScenarioID, isDuplicateSelected: selectedOption === 'duplicateScenarioForUsers'});
}
}
const addCard = () =>
setScenarioMappings((prevState) => [
...prevState,
{ scenarioID: "", duplicate: false },
]);
const handleReset = () => {
setName('');
}
const removeCard = (index) => {
const updatedMappings = scenarioMappings.filter((_, i) => i !== index);
setScenarioMappings(updatedMappings);
if (!isValid) checkValidity(name, updatedMappings);
};
const handleSelectChange = (e) => {
setSelectedScenarioID(e.target.value);
checkValidity(name, e.target.value);
};
return (<Dialog
return (
<Dialog
show={isModalOpened}
title="New User Group"
buttonTitle="Add"
onClose={handleClose}
onReset={handleReset}
valid={isValid}>
valid={isValid}
>
<Form>
<Form.Group as={Col} controlId="name" style={{marginBottom: '15px'}}>
<Form.Group as={Col} controlId="name" className="mt-2">
<Form.Label>Name</Form.Label>
<Form.Control type="text" placeholder="Enter name" value={name} onChange={handleNameChange} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group as={Col} controlId="radioGroup" style={{ marginBottom: '15px' }}>
<div>
<Form.Check
type="radio"
id="addUsersToScenario"
name="options"
label="Add users to scenario"
value="addUsersToScenario"
checked={selectedOption === 'addUsersToScenario'}
onChange={handleRadioChange}
/>
<Form.Check
type="radio"
id="duplicateScenarioForUsers"
name="options"
label="Duplicate scenario for each user"
value="duplicateScenarioForUsers"
checked={selectedOption === 'duplicateScenarioForUsers'}
onChange={handleRadioChange}
/>
</div>
<Form.Control
type="text"
placeholder="Enter name"
isInvalid={!isValid && name.length < 3}
value={name}
onChange={handleNameChange}
/>
<Form.Control.Feedback type="invalid">
Name should not at least 3 characters long
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="scenario">
<Form.Label>Select Option</Form.Label>
{isLoadingScenarios ? <div>Loading...</div> : (
<Form.Control as="select" value={selectedScenarioID} onChange={handleSelectChange}>
<option value="">-- Select scenario --</option>
{scenarios.map(scenario => <option key={scenario.id} value={scenario.id}>{scenario.name}</option>)}
</Form.Control>
)}
</Form.Group>
<Button
onClick={addCard}
className="mt-2 btn-secondary"
disabled={scenarioMappings?.length >= scenarios?.length}
>
Add Scenario Mapping
</Button>
<div className="scrollable-list">
{scenarioMappings.map((mapping, index) => (
<Card key={index} className="mt-2">
<Card.Body>
<div className="d-flex justify-content-between align-items-start">
<Form.Group
as={Col}
controlId={`radioGroup-${index}`}
style={{ marginBottom: "15px", flexGrow: 1 }}
>
<div>
<Form.Check
type="radio"
id={`addUsersToScenario-${index}`}
name={`options-${index}`}
label="Add users to scenario"
value="addUsersToScenario"
checked={!mapping.duplicate}
onChange={(e) =>
handleRadioChange(index, e.target.value)
}
/>
<Form.Check
type="radio"
id={`duplicateScenarioForUsers-${index}`}
name={`options-${index}`}
label="Duplicate scenario for each user"
value="duplicateScenarioForUsers"
checked={mapping.duplicate}
onChange={(e) =>
handleRadioChange(index, e.target.value)
}
/>
</div>
</Form.Group>
<Button
variant="danger"
onClick={() => removeCard(index)}
className="ms-auto"
>
Remove
</Button>
</div>
<Form.Group controlId={`scenario-${index}`}>
<Form.Label>Select Option</Form.Label>
{isLoadingScenarios ? (
<div>Loading...</div>
) : (
<Form.Control
as="select"
isInvalid={!isValid && mapping.scenarioID == ""}
value={mapping.scenarioID}
onChange={(e) =>
handleSelectChange(index, e.target.value)
}
>
<option value="">-- Select scenario --</option>
{scenarios.map((scenario) => {
//is there already a mapping with this scenarioID?
const isOptionUnavailable = scenarioMappings.find(
(m) => m.scenarioID == scenario.id
);
return (
<option
key={scenario.id}
value={scenario.id}
style={
isOptionUnavailable
? { backgroundColor: "lightgray" }
: {}
}
disabled={isOptionUnavailable}
>
{scenario.name}
</option>
);
})}
</Form.Control>
)}
</Form.Group>
</Card.Body>
</Card>
))}
</div>
</Form>
</Dialog>);
}
</Dialog>
);
};
export default AddUsergroupDialog;

View file

@ -16,8 +16,17 @@
******************************************************************************/
import { useState } from "react";
import { useGetUsergroupsQuery, useAddUsergroupMutation,useDeleteUsergroupMutation } from "../../store/apiSlice";
import { Table, DataColumn, LinkColumn, ButtonColumn } from "../../common/table";
import {
useGetUsergroupsQuery,
useAddUsergroupMutation,
useDeleteUsergroupMutation,
} from "../../store/apiSlice";
import {
Table,
DataColumn,
LinkColumn,
ButtonColumn,
} from "../../common/table";
import IconButton from "../../common/buttons/icon-button";
import { buttonStyle, iconStyle } from "./styles";
import AddUsergroupDialog from "./dialogs/addUsergroupDialog";
@ -28,102 +37,114 @@ import NotificationsFactory from "../../common/data-managers/notifications-facto
import { Spinner } from "react-bootstrap";
const Usergroups = () => {
const {data: {usergroups} = {}, refetch: refetchUsergroups, isLoading} = useGetUsergroupsQuery();
const [addUsergroup] = useAddUsergroupMutation();
const [deleteUsergroup] = useDeleteUsergroupMutation();
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [dialogUsegroup, setDialogUsergroup] = useState({});
const {
data: { usergroups } = {},
refetch: refetchUsergroups,
isLoading,
} = useGetUsergroupsQuery();
const [addUsergroup] = useAddUsergroupMutation();
const [deleteUsergroup] = useDeleteUsergroupMutation();
const handleAddNewGroup = async (response) => {
if(response){
try {
//put data in correct structure
const usergroup = {Name: response.name, ScenarioMappings: [{Duplicate: response.isDuplicateSelected, ScenarioID: Number(response.scenarioID)}]};
await addUsergroup(usergroup).unwrap();
refetchUsergroups();
} catch (err) {
notificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR(err.data.message));
}
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [dialogUsegroup, setDialogUsergroup] = useState({});
const handleAddNewGroup = async (response) => {
if (response) {
try {
//put data in correct structure
const usergroup = {
name: response.name,
scenarioMappings: response.scenarioMappings,
};
await addUsergroup(usergroup).unwrap();
refetchUsergroups();
} catch (err) {
notificationsDataManager.addNotification(
NotificationsFactory.UPDATE_ERROR(err.data.message)
);
}
}
setIsAddDialogOpen(false);
};
const handleDeleteUsergroup = async (isConfirmed) => {
if (isConfirmed) {
try {
await deleteUsergroup(dialogUsegroup.id);
refetchUsergroups();
} catch (err) {
notificationsDataManager.addNotification(
NotificationsFactory.UPDATE_ERROR(err.data.message)
);
}
setIsAddDialogOpen(false);
}
const handleDeleteUsergroup = async (isConfirmed) => {
if(isConfirmed){
try{
await deleteUsergroup(dialogUsegroup.id);
refetchUsergroups();
} catch (err) {
notificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR(err.data.message));
}
}
setDialogUsergroup({});
setIsDeleteDialogOpen(false);
};
setDialogUsergroup({});
setIsDeleteDialogOpen(false);
}
const stateUpdateModifier = (dateString) => {
const date = moment(dateString);
return `${date.fromNow()}`;
};
const stateUpdateModifier = (dateString) => {
const date = moment(dateString);
return `${date.fromNow()}`;
};
if(isLoading) return <Spinner />;
if (isLoading) return <Spinner />;
if(usergroups){
return (<div className="section">
if (usergroups) {
return (
<div className="section">
<h1>
User Groups
<span className="icon-button">
<IconButton
childKey={0}
tooltip="Add Usergroup"
onClick={() => setIsAddDialogOpen(true)}
icon="plus"
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
<IconButton
childKey={0}
tooltip="Add Usergroup"
onClick={() => setIsAddDialogOpen(true)}
icon="plus"
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</span>
</h1>
<Table data={usergroups}>
<DataColumn
title='ID'
dataKey='id'
width={70}
/>
<LinkColumn
title="Name"
dataKey="name"
link="/usergroup/"
linkKey="id"
/>
<DataColumn
title='Last Update'
dataKey='updatedAt'
modifier={(updatedAt) => stateUpdateModifier(updatedAt)}
/>
<ButtonColumn
width="200"
align="right"
deleteButton
onDelete={(index) => {
setDialogUsergroup(usergroups[index]);
setIsDeleteDialogOpen(true);
}}
/>
<DataColumn title="ID" dataKey="id" width={70} />
<LinkColumn
title="Name"
dataKey="name"
link="/usergroup/"
linkKey="id"
/>
<DataColumn
title="Last Update"
dataKey="updatedAt"
modifier={(updatedAt) => stateUpdateModifier(updatedAt)}
/>
<ButtonColumn
width="200"
align="right"
deleteButton
onDelete={(index) => {
setDialogUsergroup(usergroups[index]);
setIsDeleteDialogOpen(true);
}}
/>
</Table>
<AddUsergroupDialog isModalOpened={isAddDialogOpen} onClose={handleAddNewGroup} />
<AddUsergroupDialog
isModalOpened={isAddDialogOpen}
onClose={handleAddNewGroup}
/>
<DeleteDialog
title="scenario"
name={dialogUsegroup.name}
show={isDeleteDialogOpen}
onClose={(isConfirmed) => handleDeleteUsergroup(isConfirmed)}
/>
</div>);
}
}
</div>
);
}
};
export default Usergroups;

View file

@ -1,7 +1,29 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { useState } from "react";
import { useSelector } from "react-redux";
import { Dropdown, DropdownButton, Spinner, Row, Col } from 'react-bootstrap';
import { Table, ButtonColumn, CheckboxColumn, DataColumn } from "../../common/table";
import { Dropdown, DropdownButton, Spinner, Row, Col } from "react-bootstrap";
import {
Table,
ButtonColumn,
CheckboxColumn,
DataColumn,
} from "../../common/table";
import Icon from "../../common/icon";
import IconButton from "../../common/buttons/icon-button";
import NewUserDialog from "./dialogs/new-user";
@ -12,313 +34,363 @@ import { buttonStyle, iconStyle } from "./styles";
import NotificationsFactory from "../../common/data-managers/notifications-factory";
import notificationsDataManager from "../../common/data-managers/notifications-data-manager";
import Usergroups from "../usergroups/usergroups";
import {
useGetUsersQuery,
useAddUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
useGetScenariosQuery,
useAddUserToScenarioMutation,
useGetUsergroupsQuery,
useAddUserToUsergroupMutation
import {
useGetUsersQuery,
useAddUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
useGetScenariosQuery,
useAddUserToScenarioMutation,
useGetUsergroupsQuery,
useAddUserToUsergroupMutation,
} from "../../store/apiSlice";
const Users = () => {
const { user: currentUser, token: sessionToken } = useSelector(
(state) => state.auth
);
const { user: currentUser, token: sessionToken } = useSelector((state) => state.auth);
const { data: fetchedUsers, refetch: refetchUsers } = useGetUsersQuery();
const users = fetchedUsers ? fetchedUsers.users : [];
const { data: { scenarios } = [], isLoading: isLoadingScenarios } =
useGetScenariosQuery();
const { data: { usergroups } = [], isLoading: isLoadingUsergroups } =
useGetUsergroupsQuery();
const [checkedUsersIDs, setCheckedUsersIDs] = useState([]);
const [addUserMutation] = useAddUserMutation();
const [updateUserMutation] = useUpdateUserMutation();
const [deleteUserMutation] = useDeleteUserMutation();
const [addUserToScenarioMutation] = useAddUserToScenarioMutation();
const [addUserToUsergroup] = useAddUserToUsergroupMutation();
const [isNewModalOpened, setIsNewModalOpened] = useState(false);
const [isEditModalOpened, setIsEditModalOpened] = useState(false);
const [isDeleteModalOpened, setIsDeleteModalOpened] = useState(false);
const [scenario, setScenario] = useState({ name: "" });
const [usergroup, setUsergroup] = useState({ name: "" });
const [isUsersToScenarioModalOpened, setUsersToScenarioModalOpened] =
useState(false);
const [isUsersToUsegroupModalOpened, setUsersToUsegroupModalOpened] =
useState(false);
const [userToEdit, setUserToEdit] = useState({});
const [userToDelete, setUserToDelete] = useState({});
const [areAllUsersChecked, setAreAllUsersChecked] = useState(false);
const {data: fetchedUsers, refetch: refetchUsers} = useGetUsersQuery();
const users = fetchedUsers ? fetchedUsers.users : [];
const { data: {scenarios} = [], isLoading: isLoadingScenarios } = useGetScenariosQuery();
const {data: {usergroups} = [], isLoading: isLoadingUsergroups } = useGetUsergroupsQuery();
const [checkedUsersIDs, setCheckedUsersIDs] = useState([]);
const [addUserMutation] = useAddUserMutation();
const [updateUserMutation] = useUpdateUserMutation();
const [deleteUserMutation] = useDeleteUserMutation();
const [addUserToScenarioMutation] = useAddUserToScenarioMutation();
const [addUserToUsergroup] = useAddUserToUsergroupMutation();
const [isNewModalOpened, setIsNewModalOpened] = useState(false);
const [isEditModalOpened, setIsEditModalOpened] = useState(false);
const [isDeleteModalOpened, setIsDeleteModalOpened] = useState(false);
const [scenario, setScenario] = useState({name: ''});
const [usergroup, setUsergroup] = useState({name: ''});
const [isUsersToScenarioModalOpened, setUsersToScenarioModalOpened] = useState(false);
const [isUsersToUsegroupModalOpened, setUsersToUsegroupModalOpened] = useState(false);
const [userToEdit, setUserToEdit] = useState({});
const [userToDelete, setUserToDelete] = useState({});
const [areAllUsersChecked, setAreAllUsersChecked] = useState(false);
const handleAddNewUser = async (data) => {
if(data){
try {
const res = await addUserMutation({user: data});
if(res.error){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(res.error.data.message));
}
refetchUsers();
} catch (error){
if(error.data){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(error.data.message));
} else {
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR("Unknown error"));
}
}
const handleAddNewUser = async (data) => {
if (data) {
try {
const res = await addUserMutation({ user: data });
if (res.error) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(res.error.data.message)
);
}
setIsNewModalOpened(false);
}
const getIconForActiveColumn = (active) => {
return <Icon icon={active ? 'check' : 'times'} />
}
const handleEditUser = async (data) => {
if(data){
try {
await updateUserMutation(data);
refetchUsers();
} catch (error) {
if(error.data){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(error.data.message));
} else {
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR("Unknown error"));
}
}
}
refetchUsers();
setIsEditModalOpened(false);
setUserToEdit({});
} catch (error) {
if (error.data) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(error.data.message)
);
} else {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR("Unknown error")
);
}
}
}
const handleDeleteUser = async (isConfimed) => {
if(isConfimed){
try {
await deleteUserMutation(userToDelete.id);
} catch(error) {
if(error.data){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(error.data.message));
} else {
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR("Unknown error"));
}
}
}
setIsNewModalOpened(false);
};
const getIconForActiveColumn = (active) => {
return <Icon icon={active ? "check" : "times"} />;
};
const handleEditUser = async (data) => {
if (data) {
try {
await updateUserMutation(data);
refetchUsers();
setIsDeleteModalOpened(false);
setUserToDelete({});
}
const handleAddUserToScenario = async (isCanceled) => {
if(!isCanceled){
try {
for(let i = 0; i < checkedUsersIDs.length; i++){
await addUserToScenarioMutation({ scenarioID: scenario.id, username: users.find(u => u.id === checkedUsersIDs[i]).username }).unwrap();
}
} catch (error) {
if(error.data){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(error.data.message));
} else {
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR("Unknown error"));
}
}
}
setUsersToScenarioModalOpened(false);
setCheckedUsersIDs([]);
setScenario({name: ''});
setAreAllUsersChecked(false);
}
const handleAddUsersToUsergroup = async (isCanceled) => {
if(!isCanceled){
try {
for(let i = 0; i < checkedUsersIDs.length; i++){
await addUserToUsergroup({ usergroupID: usergroup.id, username: users.find(u => u.id === checkedUsersIDs[i]).username }).unwrap();
}
} catch (error) {
if(error.data){
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR(error.data.message));
} else {
console.log("error", error)
notificationsDataManager.addNotification(NotificationsFactory.LOAD_ERROR("Unknown error"));
}
}
}
setUsersToUsegroupModalOpened(false);
setCheckedUsersIDs([]);
setUsergroup({name: ''});
setAreAllUsersChecked(false);
}
const toggleCheckAllUsers = () => {
if(checkedUsersIDs.length === users.length){
setCheckedUsersIDs([]);
setAreAllUsersChecked(false);
} catch (error) {
if (error.data) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(error.data.message)
);
} else {
users.forEach(user => {
if(!checkedUsersIDs.includes(user.id)){
setCheckedUsersIDs(prevState => ([...prevState, user.id]));
}
})
setAreAllUsersChecked(true);
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR("Unknown error")
);
}
}
}
const isUserChecked = (user) => {
return checkedUsersIDs.includes(user.id);
}
refetchUsers();
setIsEditModalOpened(false);
setUserToEdit({});
};
const handleUserCheck = (user, event) => {
if(!checkedUsersIDs.includes(user.id)){
setCheckedUsersIDs(prevState => ([...prevState, user.id]));
const handleDeleteUser = async (isConfimed) => {
if (isConfimed) {
try {
await deleteUserMutation(userToDelete.id);
} catch (error) {
if (error.data) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(error.data.message)
);
} else {
const index = checkedUsersIDs.indexOf(user.id);
setCheckedUsersIDs(prevState => prevState.filter((_, i) => i !== index));
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR("Unknown error")
);
}
}
}
return (
<div>
<h1>Users
<span className='icon-button'>
<IconButton
childKey={0}
tooltip='Add User'
onClick={() => setIsNewModalOpened(true)}
icon='plus'
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</span>
</h1>
refetchUsers();
setIsDeleteModalOpened(false);
setUserToDelete({});
};
<Table
data={users}
allRowsChecked={areAllUsersChecked}
>
{currentUser.role === "Admin" ?
<DataColumn
title='ID'
dataKey='id'
/>
: <></>
}
{currentUser.role === "Admin" ?
<CheckboxColumn
enableCheckAll
onCheckAll={() => toggleCheckAllUsers()}
allChecked={areAllUsersChecked}
checked={(user) => isUserChecked(user)}
onChecked={(user, event) => handleUserCheck(user, event)}
width='30'
/>
: <></>
}
<DataColumn title='Username' width='150' dataKey='username' />
<DataColumn title='E-mail' dataKey='mail'/>
<DataColumn title='Role' dataKey='role'/>
<DataColumn title='Active' dataKey='active' modifier={(active) => getIconForActiveColumn(active)}/>
<ButtonColumn
width='200'
align='right'
editButton
deleteButton
onEdit={index => {
setIsEditModalOpened(true);
setUserToEdit(users[index]);
}}
onDelete={index => {
setIsDeleteModalOpened(true);
setUserToDelete(users[index]);
}}
/>
</Table>
const handleAddUserToScenario = async (isCanceled) => {
if (!isCanceled) {
try {
for (let i = 0; i < checkedUsersIDs.length; i++) {
await addUserToScenarioMutation({
scenarioID: scenario.id,
username: users.find((u) => u.id === checkedUsersIDs[i]).username,
}).unwrap();
}
} catch (error) {
if (error.data) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(error.data.message)
);
} else {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR("Unknown error")
);
}
}
}
<Row>
<Col md="auto">
{isLoadingScenarios? <Spinner /> : <>
<span className='solid-button'>
<DropdownButton
title='Add to Scenario'
onSelect={(id) => {
let scenario;
if(scenarios.length > 0) {
scenario = scenarios.find(s => s.id == id);
}
setScenario(scenario);
setUsersToScenarioModalOpened(true);
}}
>
{scenarios.map(scenario => (
<Dropdown.Item key={scenario.id} eventKey={scenario.id}>{scenario.name}</Dropdown.Item>
))}
</DropdownButton>
</span>
setUsersToScenarioModalOpened(false);
setCheckedUsersIDs([]);
setScenario({ name: "" });
setAreAllUsersChecked(false);
};
<UsersToScenarioDialog
show={isUsersToScenarioModalOpened}
users={users.filter(user => checkedUsersIDs.includes(user.id))}
scenario={scenario.name}
onClose={(canceled) => handleAddUserToScenario(canceled)}
/>
</>}
</Col>
const handleAddUsersToUsergroup = async (isCanceled) => {
if (!isCanceled) {
try {
for (let i = 0; i < checkedUsersIDs.length; i++) {
await addUserToUsergroup({
usergroupID: usergroup.id,
username: users.find((u) => u.id === checkedUsersIDs[i]).username,
}).unwrap();
}
} catch (error) {
if (error.data) {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR(error.data.message)
);
} else {
notificationsDataManager.addNotification(
NotificationsFactory.LOAD_ERROR("Unknown error")
);
}
}
}
<Col md="auto">
{isLoadingUsergroups? <Spinner /> : <>
<span className='solid-button'>
<DropdownButton
title='Add to Usegroup'
onSelect={(id) => {
let usergroup;
if(usergroups.length > 0) {
usergroup = usergroups.find(s => s.id == id);
}
setUsergroup(usergroup);
setUsersToUsegroupModalOpened(true);
}}
>
{usergroups.map(usergroup => (
<Dropdown.Item key={usergroup.id} eventKey={usergroup.id}>{usergroup.name}</Dropdown.Item>
))}
</DropdownButton>
</span>
setUsersToUsegroupModalOpened(false);
setCheckedUsersIDs([]);
setUsergroup({ name: "" });
setAreAllUsersChecked(false);
};
{/* re-using same modal to implement adding suers to usergroup */}
<UsersToScenarioDialog
show={isUsersToUsegroupModalOpened}
users={users.filter(user => checkedUsersIDs.includes(user.id))}
scenario={usergroup.name}
onClose={(canceled) => handleAddUsersToUsergroup(canceled)}
/>
</>}
</Col>
</Row>
const toggleCheckAllUsers = () => {
if (checkedUsersIDs.length === users.length) {
setCheckedUsersIDs([]);
setAreAllUsersChecked(false);
} else {
users.forEach((user) => {
if (!checkedUsersIDs.includes(user.id)) {
setCheckedUsersIDs((prevState) => [...prevState, user.id]);
}
});
setAreAllUsersChecked(true);
}
};
<NewUserDialog
show={isNewModalOpened}
onClose={(data) => handleAddNewUser(data)}
/>
<EditUserDialog
show={isEditModalOpened}
onClose={(data) => handleEditUser(data)}
user={userToEdit}
/>
<DeleteDialog
title="user"
name={userToDelete.username}
show={isDeleteModalOpened}
onClose={(e) => handleDeleteUser(e)}
/>
const isUserChecked = (user) => {
return checkedUsersIDs.includes(user.id);
};
<div className="mt-4">
<Usergroups />
</div>
</div>
)
}
const handleUserCheck = (user, event) => {
if (!checkedUsersIDs.includes(user.id)) {
setCheckedUsersIDs((prevState) => [...prevState, user.id]);
} else {
const index = checkedUsersIDs.indexOf(user.id);
setCheckedUsersIDs((prevState) =>
prevState.filter((_, i) => i !== index)
);
}
};
return (
<div>
<h1>
Users
<span className="icon-button">
<IconButton
childKey={0}
tooltip="Add User"
onClick={() => setIsNewModalOpened(true)}
icon="plus"
buttonStyle={buttonStyle}
iconStyle={iconStyle}
/>
</span>
</h1>
<Table data={users} allRowsChecked={areAllUsersChecked}>
{currentUser.role === "Admin" ? (
<DataColumn title="ID" dataKey="id" />
) : (
<></>
)}
{currentUser.role === "Admin" ? (
<CheckboxColumn
enableCheckAll
onCheckAll={() => toggleCheckAllUsers()}
allChecked={areAllUsersChecked}
checked={(user) => isUserChecked(user)}
onChecked={(user, event) => handleUserCheck(user, event)}
width="30"
/>
) : (
<></>
)}
<DataColumn title="Username" width="150" dataKey="username" />
<DataColumn title="E-mail" dataKey="mail" />
<DataColumn title="Role" dataKey="role" />
<DataColumn
title="Active"
dataKey="active"
modifier={(active) => getIconForActiveColumn(active)}
/>
<ButtonColumn
width="200"
align="right"
editButton
deleteButton
onEdit={(index) => {
setIsEditModalOpened(true);
setUserToEdit(users[index]);
}}
onDelete={(index) => {
setIsDeleteModalOpened(true);
setUserToDelete(users[index]);
}}
/>
</Table>
<Row>
<Col md="auto">
{isLoadingScenarios ? (
<Spinner />
) : (
<>
<span className="solid-button">
<DropdownButton
title="Add to Scenario"
onSelect={(id) => {
let scenario;
if (scenarios.length > 0) {
scenario = scenarios.find((s) => s.id == id);
}
setScenario(scenario);
setUsersToScenarioModalOpened(true);
}}
>
{scenarios.map((scenario) => (
<Dropdown.Item key={scenario.id} eventKey={scenario.id}>
{scenario.name}
</Dropdown.Item>
))}
</DropdownButton>
</span>
<UsersToScenarioDialog
show={isUsersToScenarioModalOpened}
users={users.filter((user) =>
checkedUsersIDs.includes(user.id)
)}
scenario={scenario.name}
onClose={(canceled) => handleAddUserToScenario(canceled)}
/>
</>
)}
</Col>
<Col md="auto">
{isLoadingUsergroups ? (
<Spinner />
) : (
<>
<span className="solid-button">
<DropdownButton
title="Add to Usegroup"
onSelect={(id) => {
let usergroup;
if (usergroups.length > 0) {
usergroup = usergroups.find((s) => s.id == id);
}
setUsergroup(usergroup);
setUsersToUsegroupModalOpened(true);
}}
>
{usergroups.map((usergroup) => (
<Dropdown.Item key={usergroup.id} eventKey={usergroup.id}>
{usergroup.name}
</Dropdown.Item>
))}
</DropdownButton>
</span>
{/* re-using same modal to implement adding suers to usergroup */}
<UsersToScenarioDialog
show={isUsersToUsegroupModalOpened}
users={users.filter((user) =>
checkedUsersIDs.includes(user.id)
)}
scenario={usergroup.name}
onClose={(canceled) => handleAddUsersToUsergroup(canceled)}
/>
</>
)}
</Col>
</Row>
<NewUserDialog
show={isNewModalOpened}
onClose={(data) => handleAddNewUser(data)}
/>
<EditUserDialog
show={isEditModalOpened}
onClose={(data) => handleEditUser(data)}
user={userToEdit}
/>
<DeleteDialog
title="user"
name={userToDelete.username}
show={isDeleteModalOpened}
onClose={(e) => handleDeleteUser(e)}
/>
<div className="mt-4">
<Usergroups />
</div>
</div>
);
};
export default Users;

View file

@ -25,8 +25,8 @@
--highlights: #527984;
--secondaryText: #818181;
--borderRadius: 0px;
--fontFamily: 'Helvetica Neue', Helvetica, Arial, sans-serif;
--backgroundText:#fff;
--fontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
--backgroundText: #fff;
}
* {
@ -43,7 +43,6 @@ body {
color: var(--main);
border-radius: var(--borderRadius);
font: 16px;
hyphens: auto;
}
@ -65,23 +64,22 @@ body {
padding: 0 !important;
}
a:link {
text-decoration: none;
a:link {
text-decoration: none;
}
a:visited {
text-decoration: none;
a:visited {
text-decoration: none;
}
a:hover {
text-decoration: none;
a:hover {
text-decoration: none;
}
a:active {
text-decoration: none;
a:active {
text-decoration: none;
}
.funding-logos img {
height: 70px;
margin: 0 10px;
@ -113,13 +111,11 @@ a:active {
padding: 15px 20px 20px 20px;
border-radius: var(--borderRadius);
width: auto;
min-height: 300px;
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 9px 18px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 9px 18px 0 rgba(0, 0, 0, 0.1);
}
.app-content-margin-left {
@ -148,10 +144,8 @@ a:active {
width: 160px;
border-radius: var(--borderRadius);
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 9px 18px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 9px 18px 0 rgba(0, 0, 0, 0.1);
}
.menulogo {
@ -163,17 +157,17 @@ a:active {
border-radius: var(--borderRadius);
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 9px 18px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 9px 18px 0 rgba(0, 0, 0, 0.1);
}
.menu a {
color: var(--secondarytext);
text-decoration:none;
text-decoration: none;
}
.menu a:hover, .menu a:focus {
text-decoration:none;
.menu a:hover,
.menu a:focus {
text-decoration: none;
}
.active {
@ -255,7 +249,7 @@ a:active {
}
.home-container > img {
margin: 20px;
margin: 20px;
}
/**
@ -269,18 +263,21 @@ a:active {
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border:0;
border: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
/**
* Tables
*/
.table thead,th,td {
.table thead,
th,
td {
padding: 0.5em 0.5em !important;
}
.table thead,th {
.table thead,
th {
background-color: var(--highlights);
color: #fff;
}
@ -304,11 +301,11 @@ hr {
.unselectable {
-webkit-touch-callout: none !important; /* iOS Safari */
-webkit-user-select: none !important; /* Safari */
-khtml-user-select: none !important; /* Konqueror HTML */
-moz-user-select: none !important; /* Firefox */
-ms-user-select: none !important; /* Internet Explorer/Edge */
user-select: none !important; /* Non-prefixed version, currently
-webkit-user-select: none !important; /* Safari */
-khtml-user-select: none !important; /* Konqueror HTML */
-moz-user-select: none !important; /* Firefox */
-ms-user-select: none !important; /* Internet Explorer/Edge */
user-select: none !important; /* Non-prefixed version, currently
supported by Chrome and Opera */
}
@ -363,7 +360,7 @@ hr {
display: -webkit-flex;
display: flex;
flex-direction: column;
background-color: #FFFFFF;
background-color: #ffffff;
}
.box-header {
@ -401,7 +398,8 @@ hr {
padding-right: 3px;
}
.section-header .btn-link:hover, .section-header .btn-link:focus {
.section-header .btn-link:hover,
.section-header .btn-link:focus {
text-decoration: none;
}
@ -421,22 +419,22 @@ hr {
background-color: #ffffff;
}
.section-buttons-group-center .btn{
.section-buttons-group-center .btn {
border-color: #ffffff;
background-color: #ffffff;
}
.section-buttons-group-center .btn:hover{
.section-buttons-group-center .btn:hover {
border-color: #e3e3e3;
background-color: #e3e3e3;
}
.section-buttons-group-right .btn{
.section-buttons-group-right .btn {
border-color: #ffffff;
background-color: #ffffff;
}
.section-buttons-group-right .btn:hover{
.section-buttons-group-right .btn:hover {
border-color: #e3e3e3;
background-color: #e3e3e3;
}
@ -447,52 +445,51 @@ hr {
float: left;
}
.section-buttons-group-left .btn{
.section-buttons-group-left .btn {
background-color: var(--highlights);
border-color: var(--highlights);
}
.section-buttons-group-left .btn:hover{
.section-buttons-group-left .btn:hover {
background-color: #31484f;
border-color: #31484f;
}
.drag-and-drop .btn{
.drag-and-drop .btn {
color: var(--highlights);
border-color: var(--highlights);
}
.drag-and-drop .btn:hover{
.drag-and-drop .btn:hover {
color: var(--highlights);
border-color: var(--highlights);
}
.section-buttons-group-right .rc-slider {
margin-left: 12px;
}
.solid-button .btn{
.solid-button .btn {
background-color: var(--highlights);
border-color: var(--highlights);
}
.solid-button .btn:hover{
background-color: #31484f;
.solid-button .btn:hover {
background-color: #31484f;
border-color: #31484f;
}
.solid-button .btn:disabled{
.solid-button .btn:disabled {
background-color: var(--highlights);
border-color: var(--highlights);
}
.icon-button .btn{
.icon-button .btn {
border-color: #ffffff;
background-color: #ffffff;
}
.icon-button .btn:hover{
.icon-button .btn:hover {
border-color: #e3e3e3;
background-color: #e3e3e3;
}
@ -510,12 +507,11 @@ hr {
opacity: 0.4;
}
/**
* Swagger UI
*/
.swagger-ui .wrapper {
padding: 0 !important;
padding: 0 !important;
}
.swagger-ui .info {
@ -526,3 +522,12 @@ hr {
background-color: var(--highlights);
color: #fff;
}
.scrollable-list {
max-height: 600px;
overflow-y: auto;
}
.btn-secondary {
background-color: var(--highlights) !important;
}