diff --git a/src/pages/scenarios/tables/results-table.js b/src/pages/scenarios/tables/results-table.js
index f0ff497..aca569c 100644
--- a/src/pages/scenarios/tables/results-table.js
+++ b/src/pages/scenarios/tables/results-table.js
@@ -27,6 +27,7 @@ import {Button} from "react-bootstrap";
import NotificationsFactory from "../../../common/data-managers/notifications-factory";
import notificationsDataManager from "../../../common/data-managers/notifications-data-manager";
import FileSaver from "file-saver";
+import moment from "moment";
import {
useGetResultsQuery,
useAddResultMutation,
@@ -142,6 +143,11 @@ const ResultsTable = (props) => {
setIsDeleteModalOpened(false);
setResultToDelete({});
}
+
+ const stateUpdateModifier = (dateString) => {
+ const date = moment(dateString);
+ return `${date.fromNow()}`;
+ };
return (
@@ -175,11 +181,13 @@ const ResultsTable = (props) => {
stateUpdateModifier(createdAt)}
width={200}
/>
stateUpdateModifier(updatedAt)}
width={200}
/>
.
+ ******************************************************************************/
+
+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';
+
+const AddScenarioMappingDialog = ({isDialogOpened, onClose, mappings}) => {
+
+ 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 handleRadioChange = (e) => {
+ setSelectedOption(e.target.value);
+ }
+
+ const handleClose = (canceled) => {
+ if(canceled) {
+ onClose(null);
+ } else {
+ onClose({scenarioID: Number(selectedScenarioID), duplicate: selectedOption === 'duplicateScenarioForUsers'});
+ }
+ }
+
+ const handleSelectChange = (e) => {
+ setSelectedScenarioID(e.target.value);
+ setIsValid(e.target.value !== '');
+ };
+
+ return ();
+}
+
+export default AddScenarioMappingDialog;
diff --git a/src/pages/usergroups/dialogs/addUserToUsergroupDialog.js b/src/pages/usergroups/dialogs/addUserToUsergroupDialog.js
new file mode 100644
index 0000000..64dab95
--- /dev/null
+++ b/src/pages/usergroups/dialogs/addUserToUsergroupDialog.js
@@ -0,0 +1,81 @@
+/**
+ * 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 .
+ ******************************************************************************/
+
+import React from 'react';
+import { useState, useEffect } from 'react';
+import { Form, Col, Dropdown, Badge } from 'react-bootstrap';
+import Dialog from '../../../common/dialogs/dialog';
+import { useGetUsersQuery } from '../../../store/apiSlice';
+
+const AddUserToUsergroupDialog = ({isModalOpened, onClose, currentUsers}) => {
+
+ const [selectedUsers, setSelectedusers] = useState([]);
+ const [isValid, setIsValid] = useState(false);
+
+ const {data: {users} = {}, isLoading: isLoadingUsers} = useGetUsersQuery();
+
+ const toggleUser = (event, option) => {
+ event.preventDefault();
+ if (selectedUsers.includes(option)) {
+ setSelectedusers(prevState => ([...prevState.filter((item) => item !== option)]));
+ } else {
+ setSelectedusers(prevState => ([...prevState, option]));
+ }
+ }
+
+ useEffect(() => {
+ setIsValid(selectedUsers.length > 0);
+ }, [selectedUsers]);
+
+ const handleClose = (canceled) => {
+ if(!canceled){
+ onClose(selectedUsers);
+ } else {
+ onClose([]);
+ }
+ }
+
+ const handleReset = () => {
+ setSelectedusers([]);
+ }
+
+ return (
+
+ );
+}
+
+export default AddUserToUsergroupDialog;
diff --git a/src/pages/usergroups/dialogs/renameGroupDialog.js b/src/pages/usergroups/dialogs/renameGroupDialog.js
new file mode 100644
index 0000000..cfab466
--- /dev/null
+++ b/src/pages/usergroups/dialogs/renameGroupDialog.js
@@ -0,0 +1,69 @@
+/**
+ * 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 .
+ ******************************************************************************/
+
+import React from 'react';
+import { useState, useEffect } from 'react';
+import { Form, Col, Button} from 'react-bootstrap';
+import Dialog from '../../../common/dialogs/dialog';
+
+const RenameUsergroupDialog = ({isModalOpened, onClose, oldName}) => {
+
+ const [name, setName] = useState(oldName);
+ const [isValid, setIsValid] = useState(false);
+
+ useEffect(() => {
+ if (isModalOpened && oldName) {
+ setName(oldName);
+ }
+ }, [isModalOpened, oldName]);
+
+ const handleNameChange = (e) => {
+ const newName = e.target.value;
+ setName(newName);
+ setIsValid(newName.length >= 3 && !(/^\s/.test(newName)));
+ }
+
+ const handleClose = (canceled) => {
+ if(canceled) {
+ onClose(null);
+ } else {
+ onClose(name);
+ }
+ }
+
+ const handleReset = () => {
+ setName('');
+ }
+
+ return ();
+}
+
+export default RenameUsergroupDialog;
diff --git a/src/pages/usergroups/tables/usergroup-scenarios-table.js b/src/pages/usergroups/tables/usergroup-scenarios-table.js
index b6716e2..28e270c 100644
--- a/src/pages/usergroups/tables/usergroup-scenarios-table.js
+++ b/src/pages/usergroups/tables/usergroup-scenarios-table.js
@@ -15,16 +15,60 @@
* along with VILLASweb. If not, see .
******************************************************************************/
-import { useGetUsergroupByIdQuery } from "../../../store/apiSlice";
+import { useState } from "react";
+import { useGetScenariosQuery, useGetUsergroupByIdQuery } from "../../../store/apiSlice";
import { Table, DataColumn, LinkColumn, ButtonColumn } from "../../../common/table";
import { iconStyle, buttonStyle } from "../styles";
import IconButton from "../../../common/buttons/icon-button";
+import AddScenarioMappingDialog from "../dialogs/addScenarioMappingDialog";
+import { useUpdateUsergroupMutation } from "../../../store/apiSlice";
+import DeleteDialog from "../../../common/dialogs/delete-dialog";
const UsergroupScenariosTable = ({usergroupID}) => {
- const {data: {usergroup} = {}, isLoading} = useGetUsergroupByIdQuery(usergroupID);
+ const {data: {usergroup} = {}, isLoading, refetch} = useGetUsergroupByIdQuery(usergroupID);
+ const {data: {scenarios} = {}, isLoading: isScenariosLoading } = useGetScenariosQuery();
+ const [isAddScenarioDialogOpen, setIsAddScenarioDialogOpen] = useState(false);
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+ const [mappingToDelete, setMappingToDelete] = useState({scenarioID: null});
+ const [updateUsergroup] = useUpdateUsergroupMutation();
- const handleAddScenarioMapping = () => {
+ const handleAddScenarioMapping = async (newMapping) => {
+ if(newMapping){
+ try{
+ //add new mapping while saving name and existing mappings if there are any
+ const oldMappings = usergroup.scenarioMappings.length > 0 ? [...usergroup.scenarioMappings] : [];
+ await updateUsergroup({usergroupID: usergroupID, usergroup: {name: usergroup.name, scenarioMappings: [...oldMappings, newMapping]}}).unwrap();
+ refetch();
+ } catch(error) {
+ console.log("Error updating mappings", error);
+ }
+ }
+ setIsAddScenarioDialogOpen(false);
+ }
+ const handleRemoveScenarioMapping = async (isConfirmed) => {
+ if(isConfirmed){
+ try {
+ //update usergroup with new mappings without the target
+ const newMappings = [...usergroup.scenarioMappings].filter(mapping => mapping.id !== mappingToDelete.id);
+ await updateUsergroup({usergroupID: usergroupID, usergroup: {name: usergroup.name, scenarioMappings: newMappings}}).unwrap();
+ refetch();
+ } catch (error) {
+ console.log("Error removing mapping", error);
+ }
+ }
+ setIsDeleteDialogOpen(false);
+ setMappingToDelete({scenarioID: null});
+ }
+
+ const getScenarioName = (scenarioID) => {
+ if(isScenariosLoading){
+ return Loading...
;
+ }
+
+ const scenario = scenarios.find((scenario) => scenario.id === scenarioID);
+
+ return scenario ? {scenario.name}
: unknown
;
}
const getDuplicateLabel = (duplicate) => {
@@ -40,7 +84,7 @@ const UsergroupScenariosTable = ({usergroupID}) => {
handleAddScenarioMapping()}
+ onClick={() => setIsAddScenarioDialogOpen(true)}
icon="plus"
buttonStyle={buttonStyle}
iconStyle={iconStyle}
@@ -48,14 +92,27 @@ const UsergroupScenariosTable = ({usergroupID}) => {
-
-
- getDuplicateLabel(duplicate)}/>
- {/* */}
+
+ getScenarioName(scenarioID)}/>
+ getDuplicateLabel(duplicate)}/>
+ {
+ setMappingToDelete(usergroup.scenarioMappings[index]);
+ setIsDeleteDialogOpen(true);
+ }}
+ />
+
+ handleAddScenarioMapping(newMapping)} />
+ handleRemoveScenarioMapping(isConfirmed)}
+ />
);
}
diff --git a/src/pages/usergroups/tables/usergroup-users-table.js b/src/pages/usergroups/tables/usergroup-users-table.js
index 30c1633..dd4a65f 100644
--- a/src/pages/usergroups/tables/usergroup-users-table.js
+++ b/src/pages/usergroups/tables/usergroup-users-table.js
@@ -15,16 +15,40 @@
* along with VILLASweb. If not, see .
******************************************************************************/
-import { useGetUsersByUsergroupIdQuery } from "../../../store/apiSlice";
+import { useState } from "react";
+import { useGetUsersByUsergroupIdQuery, useAddUserToUsergroupMutation, useDeleteUserFromUsergroupMutation } from "../../../store/apiSlice";
import { Table, DataColumn, LinkColumn, ButtonColumn } from "../../../common/table";
import { iconStyle, buttonStyle } from "../styles";
import IconButton from "../../../common/buttons/icon-button";
+import AddUserToUsergroupDialog from "../dialogs/addUserToUsergroupDialog";
+import NotificationsFactory from "../../../common/data-managers/notifications-factory";
+import notificationsDataManager from "../../../common/data-managers/notifications-data-manager";
const UsergroupUsersTable = ({usergroupID}) => {
- const {data: {users}=[], isLoading} = useGetUsersByUsergroupIdQuery(usergroupID);
+ const {data: {users}=[], isLoading, refetch} = useGetUsersByUsergroupIdQuery(usergroupID);
+ const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
+ const [addUserToUsergroup] = useAddUserToUsergroupMutation();
+ const [removeUserFromGroup] = useDeleteUserFromUsergroupMutation();
- const handleAddUser = () => {
+ const handleAddUser = async (selectedUsers) => {
+ if(selectedUsers.length > 0){
+ try {
+ await Promise.all(selectedUsers.map(user => addUserToUsergroup({usergroupID: usergroupID, username: user.username}).unwrap()));
+ refetch();
+ } catch (error) {
+ console.log('Error adding users', error);
+ }
+ }
+ setIsAddUserDialogOpen(false);
+ }
+ const handleRemoveUser = async (user) => {
+ try{
+ await removeUserFromGroup({usergroupID: usergroupID, username: user.username}).unwrap();
+ refetch();
+ } catch(error) {
+ console.log('Error removing users', error);
+ }
}
if(isLoading) return Loading...
;
@@ -36,7 +60,7 @@ const UsergroupUsersTable = ({usergroupID}) => {
handleAddUser()}
+ onClick={() => setIsAddUserDialogOpen(true)}
icon="plus"
buttonStyle={buttonStyle}
iconStyle={iconStyle}
@@ -44,21 +68,20 @@ const UsergroupUsersTable = ({usergroupID}) => {
-
+
+ handleRemoveUser(users[index])}
/>
-
- {/* */}
+ handleAddUser(selectedUsers)}
+ currentUsers={users}
+ />
);
}
diff --git a/src/pages/usergroups/usergroup.js b/src/pages/usergroups/usergroup.js
index 0bc2c30..ab74b17 100644
--- a/src/pages/usergroups/usergroup.js
+++ b/src/pages/usergroups/usergroup.js
@@ -15,23 +15,55 @@
* along with VILLASweb. If not, see .
******************************************************************************/
+import { useState } from "react";
import { useParams } from "react-router-dom/cjs/react-router-dom.min";
-import { Table, DataColumn, LinkColumn } from "../../common/table";
import { Row, Col } from "react-bootstrap";
import UsergroupScenariosTable from "./tables/usergroup-scenarios-table";
import UsergroupUsersTable from "./tables/usergroup-users-table";
-import { useGetUsergroupByIdQuery } from "../../store/apiSlice";
+import IconButton from "../../common/buttons/icon-button";
+import { buttonStyle, iconStyle } from "./styles";
+import { useGetUsergroupByIdQuery, useUpdateUsergroupMutation } from "../../store/apiSlice";
+import RenameUsergroupDialog from "./dialogs/renameGroupDialog";
-const Usergroup = (props) => {
+const Usergroup = () => {
const params = useParams();
const usergroupID = params.usergroup;
- const {data: {usergroup} = {}, isLoading} = useGetUsergroupByIdQuery(usergroupID);
+ const {data: {usergroup} = {}, isLoading, refetch} = useGetUsergroupByIdQuery(usergroupID);
+ const [isRenameModalOpened, setIsRenameModalOpened] = useState(false);
+ const [updateUsergroup] = useUpdateUsergroupMutation();
+
+ const handleRename = async (newName) => {
+ if(newName){
+ try {
+ //update only the name
+ await updateUsergroup({usergroupID: usergroup.id, usergroup: {name: newName, ScenarioMappings: usergroup.ScenarioMappings}}).unwrap();
+ refetch();
+ } catch (error) {
+ console.log('Error updating group', error);
+ }
+ }
+
+ setIsRenameModalOpened(false);
+ }
if(isLoading) return Loading...
;
return (
-
{usergroup.name}
+
+ {usergroup.name}
+
+ setIsRenameModalOpened(true)}
+ icon='edit'
+ buttonStyle={buttonStyle}
+ iconStyle={iconStyle}
+ />
+
+
+
@@ -40,6 +72,12 @@ const Usergroup = (props) => {
+
+
);
}
diff --git a/src/pages/usergroups/usergroups.js b/src/pages/usergroups/usergroups.js
index 942e495..2b56b26 100644
--- a/src/pages/usergroups/usergroups.js
+++ b/src/pages/usergroups/usergroups.js
@@ -22,6 +22,7 @@ import IconButton from "../../common/buttons/icon-button";
import { buttonStyle, iconStyle } from "./styles";
import AddUsergroupDialog from "./dialogs/addUsergroupDialog";
import DeleteDialog from "../../common/dialogs/delete-dialog";
+import moment from "moment";
const Usergroups = (props) => {
const {data: {usergroups} = {}, refetch: refetchUsergroups, isLoading} = useGetUsergroupsQuery();
@@ -60,6 +61,11 @@ const Usergroups = (props) => {
setDialogUsergroup({});
setIsDeleteDialogOpen(false);
}
+
+ const stateUpdateModifier = (dateString) => {
+ const date = moment(dateString);
+ return `${date.fromNow()}`;
+ };
if(isLoading) return Loading
;
@@ -69,12 +75,12 @@ const Usergroups = (props) => {
User Groups
setIsAddDialogOpen(true)}
- icon="plus"
- buttonStyle={buttonStyle}
- iconStyle={iconStyle}
+ childKey={0}
+ tooltip="Add Usergroup"
+ onClick={() => setIsAddDialogOpen(true)}
+ icon="plus"
+ buttonStyle={buttonStyle}
+ iconStyle={iconStyle}
/>
@@ -90,6 +96,11 @@ const Usergroups = (props) => {
link="/usergroup/"
linkKey="id"
/>
+ stateUpdateModifier(updatedAt)}
+ />
{
handleDeleteUsergroup(isConfirmed)}
- />
+ title="scenario"
+ name={dialogUsegroup.name}
+ show={isDeleteDialogOpen}
+ onClose={(isConfirmed) => handleDeleteUsergroup(isConfirmed)}
+ />
);
}
}
diff --git a/src/store/apiSlice.js b/src/store/apiSlice.js
index 57955ee..aa71ef0 100644
--- a/src/store/apiSlice.js
+++ b/src/store/apiSlice.js
@@ -110,5 +110,8 @@ export const {
useAddUsergroupMutation,
useDeleteUsergroupMutation,
useGetUsergroupByIdQuery,
- useGetUsersByUsergroupIdQuery
+ useGetUsersByUsergroupIdQuery,
+ useAddUserToUsergroupMutation,
+ useDeleteUserFromUsergroupMutation,
+ useUpdateUsergroupMutation
} = apiSlice;
diff --git a/src/store/endpoints/usergroup-endpoints.js b/src/store/endpoints/usergroup-endpoints.js
index 8fbaf9b..65d5d8d 100644
--- a/src/store/endpoints/usergroup-endpoints.js
+++ b/src/store/endpoints/usergroup-endpoints.js
@@ -31,6 +31,27 @@ export const usergroupEndpoints = (builder) => ({
},
}),
}),
+ updateUsergroup: builder.mutation({
+ query: ({ usergroupID, usergroup }) => ({
+ url: `/usergroups/${usergroupID}`,
+ method: 'PUT',
+ body: { usergroup },
+ }),
+ }),
+ addUserToUsergroup: builder.mutation({
+ query: ({ usergroupID, username }) => ({
+ url: `/usergroups/${usergroupID}/user`,
+ method: 'PUT',
+ params: { username },
+ }),
+ }),
+ deleteUserFromUsergroup: builder.mutation({
+ query: ({ usergroupID, username }) => ({
+ url: `/usergroups/${usergroupID}/user`,
+ method: 'DELETE',
+ params: { username },
+ }),
+ }),
getUsersByUsergroupId: builder.query({
query: (usergroupID) => `/usergroups/${usergroupID}/users`,
}),