diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj index bf230bd4e..4b498515e 100644 --- a/csharp/App/Backend/Backend.csproj +++ b/csharp/App/Backend/Backend.csproj @@ -13,6 +13,7 @@ + @@ -39,6 +40,200 @@ PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index ada5ed86a..7adf7d751 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -197,7 +197,10 @@ public class Controller : ControllerBase if (user == null) return Unauthorized(); - return user.DescendantUsers().Select(u => u.HidePassword()).ToList(); + return user + .DescendantUsers() + .Select(u => u.HidePassword()) + .ToList(); } @@ -252,7 +255,7 @@ public class Controller : ControllerBase [HttpPost(nameof(CreateUser))] - public ActionResult CreateUser(User newUser, Token authToken) + public ActionResult CreateUser([FromBody] User newUser, Token authToken) { return Db.GetSession(authToken).Create(newUser) ? newUser.HidePassword() @@ -260,7 +263,7 @@ public class Controller : ControllerBase } [HttpPost(nameof(CreateInstallation))] - public async Task> CreateInstallation([FromBody]Installation installation, Token authToken) + public async Task> CreateInstallation([FromBody] Installation installation, Token authToken) { var session = Db.GetSession(authToken); @@ -271,7 +274,7 @@ public class Controller : ControllerBase } [HttpPost(nameof(CreateFolder))] - public ActionResult CreateFolder(Folder folder, Token authToken) + public ActionResult CreateFolder([FromBody] Folder folder, Token authToken) { var session = Db.GetSession(authToken); @@ -331,7 +334,7 @@ public class Controller : ControllerBase var session = Db.GetSession(authToken); // TODO: automatic BadRequest when properties are null during deserialization - var installation = Db.GetFolderById(installationAccess.InstallationId); + var installation = Db.GetInstallationById(installationAccess.InstallationId); var user = Db.GetUserById(installationAccess.UserId); return session.RevokeUserAccessTo(user, installation) @@ -342,7 +345,7 @@ public class Controller : ControllerBase [HttpPut(nameof(UpdateUser))] - public ActionResult UpdateUser(User updatedUser, Token authToken) + public ActionResult UpdateUser([FromBody] User updatedUser, Token authToken) { var session = Db.GetSession(authToken); @@ -366,7 +369,7 @@ public class Controller : ControllerBase [HttpPut(nameof(UpdateInstallation))] - public ActionResult UpdateInstallation(Installation installation, Token authToken) + public ActionResult UpdateInstallation([FromBody] Installation installation, Token authToken) { var session = Db.GetSession(authToken); @@ -378,7 +381,7 @@ public class Controller : ControllerBase [HttpPut(nameof(UpdateFolder))] - public ActionResult UpdateFolder(Folder folder, Token authToken) + public ActionResult UpdateFolder([FromBody] Folder folder, Token authToken) { var session = Db.GetSession(authToken); diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 71fb0d640..492558c54 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -9,8 +9,9 @@ public class Installation : TreeNode public String Country { get; set; } = ""; // TODO: make relation - [Ignore] public IReadOnlyList OrderNumbers { get; set; } = Array.Empty(); - + //public IReadOnlyList OrderNumbers { get; set; } = Array.Empty(); + public String OrderNumbers { get; set; } = ""; + public Double Lat { get; set; } public Double Long { get; set; } diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs index a71960f52..8098b53c0 100644 --- a/csharp/App/Backend/DataTypes/Methods/Folder.cs +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -52,6 +52,11 @@ public static class FolderMethods .Skip(1); // skip self } + public static IEnumerable DescendantFoldersAndSelf(this Folder parent) + { + return parent + .TraverseDepthFirstPreOrder(ChildFolders); + } public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) { return folder diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index fbae86041..cf6bdb014 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -1,4 +1,5 @@ using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.Relations; using InnovEnergy.App.Backend.S3; using InnovEnergy.Lib.Utils; @@ -119,12 +120,28 @@ public static class InstallationMethods return Db.Installations.Any(i => i.Id == installation.Id); } - public static IReadOnlyList GetOrderNumbers(this Installation installation) + public static Boolean SetOrderNumbers(this Installation installation) { - return Db.OrderNumber2Installation + foreach (var orderNumber in installation.OrderNumbers.Split(',')) + { + + var o2I = new OrderNumber2Installation + { + OrderNumber = orderNumber, + InstallationId = installation.Id + }; + Db.Create(o2I); + } + + return true; + } + + public static String GetOrderNumbers(this Installation installation) + { + return string.Join(", ", Db.OrderNumber2Installation .Where(i => i.InstallationId == installation.Id) .Select(i => i.OrderNumber) - .ToReadOnlyList(); + .ToReadOnlyList()); } public static Installation FillOrderNumbers(this Installation installation) diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 8f2ee1191..f2a1540c3 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -83,18 +83,19 @@ public static class SessionMethods var user = session?.User; return user is not null - && installation is not null - && user.HasWriteAccess - && user.HasAccessToParentOf(installation) - && Db.Create(installation) // TODO: these two in a transaction - && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }) - && await installation.CreateBucket() - && await installation.RenewS3Credentials(); // generation of access _after_ generation of - // bucket to prevent "zombie" access-rights. - // This might fuck us over if the creation of access rights fails, - // as bucket-names are unique and bound to the installation id... -K + && installation is not null + && user.HasWriteAccess + && user.HasAccessToParentOf(installation) + && Db.Create(installation) // TODO: these two in a transaction + && installation.SetOrderNumbers() + && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }); + //&& await installation.CreateBucket() + //&& await installation.RenewS3Credentials(); // generation of access _after_ generation of + // bucket to prevent "zombie" access-rights. + // This might fuck us over if the creation of access rights fails, + // as bucket-names are unique and bound to the installation id... -K } - + public static Boolean Update(this Session? session, Installation? installation) { var user = session?.User; @@ -104,7 +105,7 @@ public static class SessionMethods if (!Equals(originalOrderNumbers, installation?.OrderNumbers)) { - foreach (var orderNumber in installation!.OrderNumbers) + foreach (var orderNumber in installation!.OrderNumbers.Split(',')) { if (originalOrderNumbers.Contains(orderNumber)) continue; var o2I = new OrderNumber2Installation @@ -115,7 +116,7 @@ public static class SessionMethods Db.Create(o2I); } - foreach (var orderNumberOld in originalOrderNumbers) + foreach (var orderNumberOld in originalOrderNumbers.Split(',')) { if (!installation!.OrderNumbers.Contains(orderNumberOld)) { @@ -138,13 +139,13 @@ public static class SessionMethods public static async Task Delete(this Session? session, Installation? installation) { var user = session?.User; - + return user is not null - && installation is not null - && user.HasWriteAccess - && user.HasAccessTo(installation) - && Db.Delete(installation) - && await installation.DeleteBucket(); + && installation is not null + && user.HasWriteAccess + && user.HasAccessTo(installation) + && Db.Delete(installation); + //&& await installation.DeleteBucket(); } public static Boolean Create(this Session? session, User newUser) @@ -158,7 +159,7 @@ public static class SessionMethods && newUser .WithParent(sessionUser) .Do(() => newUser.MustResetPassword = true) - .Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password)) + .Do(() => newUser.Password = null) .Apply(Db.Create); // && Mailer.Mailer.SendVerificationMessage(newUser); diff --git a/csharp/App/Backend/DataTypes/TreeNode.cs b/csharp/App/Backend/DataTypes/TreeNode.cs index b1a4a72fa..5613fbbdf 100644 --- a/csharp/App/Backend/DataTypes/TreeNode.cs +++ b/csharp/App/Backend/DataTypes/TreeNode.cs @@ -5,7 +5,7 @@ namespace InnovEnergy.App.Backend.DataTypes; public abstract partial class TreeNode { [PrimaryKey, AutoIncrement] - public virtual Int64 Id { get; set; } + public virtual Int64 Id { get; set; } public virtual String Name { get; set; } = ""; // overridden by User (unique) public String Information { get; set; } = ""; // unstructured random info diff --git a/csharp/App/Backend/DataTypes/User.cs b/csharp/App/Backend/DataTypes/User.cs index a9c6b4bdd..7397eb6e2 100644 --- a/csharp/App/Backend/DataTypes/User.cs +++ b/csharp/App/Backend/DataTypes/User.cs @@ -8,7 +8,7 @@ public class User : TreeNode public Boolean HasWriteAccess { get; set; } = false; public Boolean MustResetPassword { get; set; } = false; public String Language { get; set; } = null!; - public String Password { get; set; } = null!; + public String? Password { get; set; } = null!; [Unique] public override String Name { get; set; } = null!; diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 0a630bc50..ed95f6340 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -27,7 +27,8 @@ public static partial class Db .Last().Name; var fileConnection = new SQLiteConnection("DbBackups/"+latestDb); - + + Console.Out.Write(latestDb); var memoryConnection = new SQLiteConnection(":memory:"); // fileConnection.Backup(memoryConnection.DatabasePath); @@ -74,7 +75,7 @@ public static partial class Db public static void BackupDatabase() { var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite"; - Connection.Backup("DbBackups/"+filename); + Connection.Backup("DbBackups/" + filename); } public static TableQuery Sessions => Connection.Table(); diff --git a/csharp/App/Backend/Database/Delete.cs b/csharp/App/Backend/Database/Delete.cs index 55ff12f84..cb3895037 100644 --- a/csharp/App/Backend/Database/Delete.cs +++ b/csharp/App/Backend/Database/Delete.cs @@ -10,12 +10,18 @@ public static partial class Db { public static Boolean Delete(Folder folder) { - return RunTransaction(DeleteFolderAndAllItsDependencies); + var deleteSuccess= RunTransaction(DeleteFolderAndAllItsDependencies); + if (deleteSuccess) + { + BackupDatabase(); + } + + return deleteSuccess; Boolean DeleteFolderAndAllItsDependencies() { return folder - .DescendantFolders() + .DescendantFoldersAndSelf() .All(DeleteDescendantFolderAndItsDependencies); } @@ -24,10 +30,8 @@ public static partial class Db FolderAccess .Delete(r => r.FolderId == f.Id); Installations.Delete(r => r.ParentId == f.Id); var delete = Folders.Delete(r => r.Id == f.Id); - var deleteSuccess = delete > 0; - if (deleteSuccess) - BackupDatabase(); - return deleteSuccess; + + return delete>0; } } @@ -42,6 +46,7 @@ public static partial class Db Boolean DeleteInstallationAndItsDependencies() { InstallationAccess.Delete(i => i.InstallationId == installation.Id); + OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id); return Installations.Delete(i => i.Id == installation.Id) > 0; } } diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 3e49baeb2..f39e5e463 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -3,6 +3,8 @@ using InnovEnergy.App.Backend.Database; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; +using System.Net; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend; @@ -12,8 +14,8 @@ public static class Program { //Db.CreateFakeRelations(); Db.Init(); - var builder = WebApplication.CreateBuilder(args); + builder.Services.AddControllers(); builder.Services.AddProblemDetails(setup => { @@ -21,7 +23,7 @@ public static class Program setup.IncludeExceptionDetails = (ctx, env) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging(); //This handles our Exceptions - setup.Map(exception => new ProblemDetails() + setup.Map(exception => new ProblemDetails { Detail = exception.Detail, Status = exception.Status, @@ -38,6 +40,16 @@ public static class Program }); var app = builder.Build(); + + app.Use(async (context, next) => + { + var x = 2; + + context.Request.WriteLine(); + + await next(context); + }); + app.UseForwardedHeaders(new ForwardedHeadersOptions { @@ -51,12 +63,11 @@ public static class Program } app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ; - app.UseHttpsRedirection(); + //app.UseHttpsRedirection(); app.MapControllers(); app.UseProblemDetails(); - - app.Run(); + app.Run(); } private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo diff --git a/csharp/App/Backend/exoscale.toml b/csharp/App/Backend/S3/exoscale.toml similarity index 100% rename from csharp/App/Backend/exoscale.toml rename to csharp/App/Backend/S3/exoscale.toml diff --git a/csharp/App/VrmGrabber/server.py b/csharp/App/VrmGrabber/server.py index 27abdbd52..5d5c350ec 100644 --- a/csharp/App/VrmGrabber/server.py +++ b/csharp/App/VrmGrabber/server.py @@ -3,7 +3,7 @@ from flask import Flask from json2html import json2html app = Flask(__name__) -serverUrl = "https://127.0.0.1:7087/api" #todo change me +serverUrl = "https://127.0.0.1:8000/api" #todo change me @app.route('/') def hello(): diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json index 97d6cfe9c..6e32e3d2f 100644 --- a/typescript/Frontend/package.json +++ b/typescript/Frontend/package.json @@ -35,6 +35,7 @@ "yup": "^1.1.0" }, "scripts": { + "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", diff --git a/typescript/Frontend/src/App.tsx b/typescript/Frontend/src/App.tsx index ea3ca8592..e7216a7c5 100644 --- a/typescript/Frontend/src/App.tsx +++ b/typescript/Frontend/src/App.tsx @@ -1,11 +1,11 @@ import useToken from "./hooks/useToken"; import Login from "./Login"; -import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; +import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; -import { Container, Grid, Box } from "@mui/material"; +import {Box, Container, Grid} from "@mui/material"; import routes from "./routes.json"; -import { IntlProvider } from "react-intl"; -import { useContext, useState } from "react"; +import {IntlProvider} from "react-intl"; +import {useContext, useState} from "react"; import en from "./lang/en.json"; import de from "./lang/de.json"; import fr from "./lang/fr.json"; @@ -14,10 +14,10 @@ import LogoutButton from "./components/Layout/LogoutButton"; import Users from "./components/Users/Users"; import NavigationTabs from "./components/Layout/NavigationTabs"; import InstallationPage from "./components/Installations/InstallationPage"; -import { UserContext } from "./components/Context/UserContextProvider"; +import {UserContext} from "./components/Context/UserContextProvider"; import ResetPassword from "./ResetPassword"; import innovenergyLogo from "./resources/innoveng_logo_on_orange.png"; -import { colors } from "./index"; +import {colors} from "./index"; const App = () => { const { token, setToken, removeToken } = useToken(); @@ -51,8 +51,10 @@ const App = () => { > - - innovenergy logo + + + innovenergy logo + void; fetchData: (timestamp: UnixTime) => Promise>; } - + export const S3CredentialsContext = createContext({ s3Credentials: {} as I_S3Credentials, @@ -27,7 +27,7 @@ const S3CredentialsContextProvider = ({ const [s3Credentials, setS3Credentials] = useState(); const saveS3Credentials = (credentials: I_S3Credentials, id: string) => { - const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c10d"; + const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d"; setS3Credentials({ s3Bucket, ...credentials }); }; diff --git a/typescript/Frontend/src/components/Context/UserContextProvider.tsx b/typescript/Frontend/src/components/Context/UserContextProvider.tsx index 2a1ea29b5..f7a83464c 100644 --- a/typescript/Frontend/src/components/Context/UserContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/UserContextProvider.tsx @@ -1,6 +1,7 @@ import { createContext, ReactNode, useState } from "react"; import { I_User } from "../../util/user.util"; + interface I_InstallationContextProviderProps { currentUser?: I_User; setCurrentUser: (value: I_User) => void; diff --git a/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx index e780ab854..307ab6118 100644 --- a/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx +++ b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx @@ -33,7 +33,7 @@ const InstallationForm = (props: I_InstallationFormProps) => { const readOnly = !getCurrentUser().hasWriteAccess; const intl = useIntl(); - + const validationSchema = Yup.object().shape({ name: Yup.string().required( intl.formatMessage({ @@ -53,6 +53,19 @@ const InstallationForm = (props: I_InstallationFormProps) => { defaultMessage: "Location is required", }) ), + country: Yup.string().required( + intl.formatMessage({ + id: "requiredCountry", + defaultMessage: "Country is required", + }) + ), + orderNumbers: Yup.string().required( + intl.formatMessage({ + id: "requiredOrderNumber", + defaultMessage: "Order Number is required", + }) + ), + }); const formik = useFormik({ @@ -64,6 +77,12 @@ const InstallationForm = (props: I_InstallationFormProps) => { orderNumbers: values.orderNumbers, }, onSubmit: (formikValues) => { + /*const updatedValues = { + ...formikValues, + + orderNumbers: formikValues.orderNumbers.split(','), + };*/ + handleSubmit(values, formikValues) .then(() => { setOpen(true); @@ -164,12 +183,22 @@ const InstallationForm = (props: I_InstallationFormProps) => { additionalButtons.map((button) => button)} {!readOnly && ( + + )} + {!readOnly && ( + + + + )} { ); }; -export default Installations; +export default Installations; \ No newline at end of file diff --git a/typescript/Frontend/src/components/Layout/NavigationTabs.tsx b/typescript/Frontend/src/components/Layout/NavigationTabs.tsx index b74f31a0d..bf8de966f 100644 --- a/typescript/Frontend/src/components/Layout/NavigationTabs.tsx +++ b/typescript/Frontend/src/components/Layout/NavigationTabs.tsx @@ -16,12 +16,7 @@ const NavigationTabs = () => { return ( <> diff --git a/typescript/Frontend/src/lang/en.json b/typescript/Frontend/src/lang/en.json index 3cbbc6634..2751313ca 100644 --- a/typescript/Frontend/src/lang/en.json +++ b/typescript/Frontend/src/lang/en.json @@ -2,6 +2,7 @@ "liveView": "Live view", "allInstallations": "All installations", "applyChanges": "Apply changes", + "deleteInstallation": "Delete Installation", "country": "Country", "customerName": "Customer name", "english": "English", @@ -44,6 +45,7 @@ "requiredLocation": "Location is required", "requiredName": "Name is required", "requiredRegion": "Region is required", + "requiredOrderNumber": "Required Order Number", "submit": "Submit", "user": "User", "userTabs": "user tabs" diff --git a/typescript/frontend-marios2/src/Resources/axiosConfig.tsx b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx new file mode 100644 index 000000000..7406652c7 --- /dev/null +++ b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx @@ -0,0 +1,26 @@ +import axios from 'axios'; + +export const axiosConfigWithoutToken = axios.create({ + baseURL: 'https://localhost:7087/api' +}); + +const axiosConfig = axios.create({ + baseURL: 'https://localhost:7087/api' +}); + +axiosConfig.defaults.params = {}; +axiosConfig.interceptors.request.use( + (config) => { + const tokenString = localStorage.getItem('token'); + const token = tokenString !== null ? tokenString : ''; + if (token) { + config.params['authToken'] = token; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +export default axiosConfig; diff --git a/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png b/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png new file mode 100644 index 000000000..4f1714ecd Binary files /dev/null and b/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png differ diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx new file mode 100644 index 000000000..452f81c9c --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import { + Card, + Divider, + Grid, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useTheme +} from '@mui/material'; +import { InnovEnergyUser } from 'src/interfaces/UserTypes'; +import User from './User'; + +interface FlatUsersViewProps { + users: InnovEnergyUser[]; + fetchDataAgain: () => void; +} + +const FlatUsersView = (props: FlatUsersViewProps) => { + const [selectedUser, setSelectedUser] = useState(-1); + const selectedBulkActions = selectedUser !== -1; + + const handleSelectOneUser = (installationID: number): void => { + if (selectedUser != installationID) { + setSelectedUser(installationID); + } else { + setSelectedUser(-1); + } + }; + + const theme = useTheme(); + const [isRowHovered, setHoveredRow] = useState(-1); + + const handleRowMouseEnter = (id: number) => { + setHoveredRow(id); + }; + + const handleRowMouseLeave = () => { + setHoveredRow(-1); + }; + const findUser = (id: number) => { + return props.users.find((user) => user.id === id); + }; + + return ( + + + + + + + + + + Username + Email + + + + {props.users.map((user) => { + const isInstallationSelected = user.id === selectedUser; + const rowStyles = + isRowHovered === user.id + ? { + cursor: 'pointer', + backgroundColor: theme.colors.primary.lighter // Set your desired hover background color here + } + : {}; + + return ( + handleSelectOneUser(user.id)} + style={rowStyles} + onMouseEnter={() => handleRowMouseEnter(user.id)} + onMouseLeave={() => handleRowMouseLeave()} + > + + + + {user.name} + + + + + {user.email} + + + + ); + })} + +
+
+
+
+ + {selectedBulkActions && ( + + )} +
+ ); +}; + +export default FlatUsersView; diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx new file mode 100644 index 000000000..3ae60674a --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx @@ -0,0 +1,90 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { + FormControl, + Grid, + InputAdornment, + TextField, + useTheme +} from '@mui/material'; +import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; +import FlatUsersView from './FlatUsersView'; +import { UsersContext } from '../../../contexts/UsersContextProvider'; +import Button from '@mui/material/Button'; +import UserForm from './userForm'; +import { UserContext } from '../../../contexts/userContext'; + +function UsersSearch() { + const theme = useTheme(); + const [searchTerm, setSearchTerm] = useState(''); + const { availableUsers, fetchAvailableUsers } = useContext(UsersContext); + const [filteredData, setFilteredData] = useState(availableUsers); + const [openModal, setOpenModal] = useState(false); + const context = useContext(UserContext); + const { currentUser, setUser } = context; + + useEffect(() => { + fetchAvailableUsers(); + }, []); + + const fetchDataAgain = () => { + fetchAvailableUsers(); + }; + + useEffect(() => { + const filtered = availableUsers.filter((item) => + item.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + setFilteredData(filtered); + }, [searchTerm, availableUsers]); + + const handleSubmit = () => { + setOpenModal(true); + }; + const handleUserFormSubmit = () => { + setOpenModal(false); + fetchAvailableUsers(); + }; + + const handleUserFormCancel = () => { + setOpenModal(false); + }; + + return ( + <> + + + {currentUser.hasWriteAccess && ( + + )} + + + {openModal && ( + + )} + + + + setSearchTerm(e.target.value)} + fullWidth + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + + + + ); +} + +export default UsersSearch; diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx new file mode 100644 index 000000000..5980287f0 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx @@ -0,0 +1,25 @@ +import Footer from 'src/components/Footer'; +import { Box, Container, Grid, useTheme } from '@mui/material'; +import UsersSearch from './UsersSearch'; +import UsersContextProvider from 'src/contexts/UsersContextProvider'; + +function Users() { + const theme = useTheme(); + + return ( + <> + + + + + + + + +