diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..6545e4d1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "C_Cpp.intelliSenseEngineFallback": "enabled"
+}
\ No newline at end of file
diff --git a/backend/src/routes/userRoute.js b/backend/src/routes/userRoute.js
index 2fc61fcb..981e3ecb 100644
--- a/backend/src/routes/userRoute.js
+++ b/backend/src/routes/userRoute.js
@@ -159,6 +159,18 @@ router.post("/resetpassword", (req, res) => {
}
});
+router.post("/adminReset", authorize(true), (req, res) => {
+ // Reset Password
+ userController
+ .updateUserPassword(req, req.body.email)
+ .then((response) => {
+ res.status(200).json(response);
+ })
+ .catch((err) => {
+ res.status(404).json(err);
+ });
+});
+
router.delete("/:userId", authorize(), (req, res) => {
userController
.deleteUser(req.params.userId)
diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
index 9434048f..ce487bcb 100644
--- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
+++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css
@@ -1,7 +1,7 @@
#invoice-template {
color: #000;
margin: 10px auto;
- border: 1px solid #ddd;
+ padding: 10px;
}
#invoice-header {
font-size: 12px;
diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
index 57b8cef1..45c15a33 100644
--- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
+++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js
@@ -102,7 +102,7 @@ const InvoiceTemplate = ({ customer, costcenterMap, invoicNum }) => {
/>
-
*/}
@@ -175,10 +175,6 @@ function InvoiceDetails({ billingData }) {
Total Due: |
${subtotal} |
-
- Amount Paid: |
- $0 |
-
diff --git a/frontend/src/components/UserManagementTable/index.css b/frontend/src/components/UserManagementTable/index.css
index 0b32f4ff..2107abb6 100644
--- a/frontend/src/components/UserManagementTable/index.css
+++ b/frontend/src/components/UserManagementTable/index.css
@@ -20,11 +20,27 @@
border: none;
}
+.userTable .ant-table-footer {
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+ background-color: #ECEDF3;
+}
+
.addService {
display: flex;
align-items: center;
gap: 12px;
}
+
+.resetPasswordButton{
+ text-align: center;
+ cursor: pointer;
+}
+
+.SaveNewPassword{
+ cursor: pointer;
+ margin-top: 10px;;
+}
.ServiceQuestionSelect {
border: 0;
background-color: #eeeeee;
@@ -86,10 +102,10 @@ So try and beat it with CSS specificity */
border: 1px solid #d9d9d9;
}
.ant-table-container,
-.ant-table-container table > tbody > tr:last-child td:first-child {
- border-bottom-left-radius: 10px;
+.userTable .ant-table-container table > tbody > tr:last-child td:first-child {
+ border-bottom-left-radius: 0px;
}
.ant-table-container,
-.ant-table-container table > tbody > tr:last-child td:last-child {
- border-bottom-right-radius: 10px;
+.userTable .ant-table-container table > tbody > tr:last-child td:last-child {
+ border-bottom-right-radius: 0px;
}
diff --git a/frontend/src/components/UserManagementTable/index.js b/frontend/src/components/UserManagementTable/index.js
index d778d6e9..8ef3f717 100644
--- a/frontend/src/components/UserManagementTable/index.js
+++ b/frontend/src/components/UserManagementTable/index.js
@@ -1,10 +1,12 @@
import React, { useState, useRef, useContext, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { Table, Form, Popconfirm, Input, Select } from "antd";
+import { Table, Form, Popconfirm, Input, Select, Popover } from "antd";
import "antd/dist/antd.min.css";
import "./index.css";
-import { DELETE_USER, GET_ORGANIZATION, LOAD_USERLIST, UPDATE_USER } from "../../redux/actions/userActions";
+import Add from "../../assets/AddBlack.png";
+import { ADMIN_RESET_PASSWORD, DELETE_USER, GET_ORGANIZATION, LOAD_USERLIST, POST_USER, UPDATE_USER } from "../../redux/actions/userActions";
import RejectUser from "../../assets/RejectUser.png";
+import uuid from "react-uuid";
const UserManagementTable = () => {
const columns = [
@@ -87,6 +89,43 @@ const UserManagementTable = () => {
/>
) : null,
},
+ {
+ title: "",
+ dataIndex: "operation",
+ render: (_, record) =>
+ dataSource.length >= 1 ? (
+
+
+ {
+ const password = document.getElementsByClassName("LoginInput")[0].value;
+ if (password && password !== "") {
+ dispatch({
+ type: ADMIN_RESET_PASSWORD,
+ payload: {
+ email: record.email,
+ password: password,
+ },
+ });
+ document.getElementsByClassName("LoginInput")[0].value = "";
+ setResetPasswordRecord(null);
+ }
+ }}>Save
+
+ }
+ title="Change User's Password"
+ trigger="click"
+ open={resetPasswordRecord === record.user_id}
+ onOpenChange={(open) => {
+ setResetPasswordRecord(open ? record.user_id : null);
+ }}
+ >
+ Reset Password
+
+ ) : null,
+ width: "5%",
+ },
{
title: "",
dataIndex: "operation",
@@ -111,6 +150,7 @@ const UserManagementTable = () => {
];
const dispatch = useDispatch();
+ const [resetPasswordRecord, setResetPasswordRecord] = useState();
const dataSource = useSelector((state) => state.userReducer.userList);
const orgList = useSelector((state) => state.userReducer.organizationList);
@@ -125,6 +165,22 @@ const UserManagementTable = () => {
const handleSave = (row) => {
dispatch({ type: UPDATE_USER, payload: row });
};
+ const handleAdd = () => {
+ const newID = uuid();
+ const newData = {
+ user_id: newID,
+ fk_organization_id: null,
+ username: "New User",
+ email: "New User Email",
+ employee: false,
+ password: "password",
+ adminCreated: true,
+ };
+ dispatch({
+ type: POST_USER,
+ payload: newData
+ });
+ };
const EditableContext = React.createContext(null);
@@ -234,6 +290,14 @@ const UserManagementTable = () => {
rowClassName={(_, index) => index % 2 === 0 ? "editable-row" : "editable-row-dark"}
dataSource={dataSource}
columns={renderedColumns}
+ footer={() => {
+ return (
+
+
+
Add
+
+ )
+ }}
/>
);
};
diff --git a/frontend/src/redux/actions/userActions.js b/frontend/src/redux/actions/userActions.js
index 6a4e5e9e..afb16805 100644
--- a/frontend/src/redux/actions/userActions.js
+++ b/frontend/src/redux/actions/userActions.js
@@ -5,6 +5,7 @@ export const POST_USER = "POST_USER";
export const UPDATE_USER = "UPDATE_USER";
export const REQUEST_RESET = "REQUEST_RESET";
export const RESET_PASSWORD = "RESET_PASSWORD";
+export const ADMIN_RESET_PASSWORD = "ADMIN_RESET_PASSWORD";
export const SIGNUP_USER = "SIGNUP_USER";
export const AUTHENTICATE_USER = "AUTHENTICATE_USER";
export const SET_CURRENT_USER = "SET_CURRENT_USER";
diff --git a/frontend/src/redux/api/userApi.js b/frontend/src/redux/api/userApi.js
index 8149078b..26ff10e8 100644
--- a/frontend/src/redux/api/userApi.js
+++ b/frontend/src/redux/api/userApi.js
@@ -120,6 +120,27 @@ export const resetPasswordApi = async (payload) => {
}
};
+export const adminResetPasswordApi = async (payload) => {
+ try {
+ const token = JSON.parse(localStorage.getItem("currentUser")).token;
+ var headers = {
+ Authorization: `Bearer ${token}`,
+ };
+ var data = JSON.stringify({
+ email: payload.email,
+ password: payload.password,
+ });
+
+ const user = await axios.post("user/adminReset", data, {
+ headers: headers,
+ });
+
+ return user;
+ } catch (err) {
+ return console.error(err);
+ }
+};
+
export const updateUserApi = async (payload) => {
try {
const token = JSON.parse(localStorage.getItem("currentUser")).token;
diff --git a/frontend/src/redux/saga/userSaga.js b/frontend/src/redux/saga/userSaga.js
index f03ab3de..c292a514 100644
--- a/frontend/src/redux/saga/userSaga.js
+++ b/frontend/src/redux/saga/userSaga.js
@@ -23,6 +23,7 @@ import {
UPDATE_USER,
REQUEST_RESET,
RESET_PASSWORD,
+ ADMIN_RESET_PASSWORD,
} from "../actions/userActions";
import { getProjectAssignmentApi } from "../api/projectApi";
import {
@@ -41,6 +42,7 @@ import {
updateUserApi,
requestResetApi,
resetPasswordApi,
+ adminResetPasswordApi,
} from "../api/userApi";
import { ErrorToast, SuccessToast } from "../../components/Toasts";
@@ -110,9 +112,24 @@ export function* resetPasswordSaga({ payload }) {
}
}
+export function* adminResetPasswordSaga({ payload }) {
+ const resetRes = yield call(adminResetPasswordApi, payload);
+ if (resetRes) {
+ SuccessToast("Password reset successfully!");
+ } else {
+ ErrorToast("Password reset failed. Please try again.");
+ }
+}
+
export function* postUserSaga({ payload }) {
const newUser = yield call(saveUserApi, payload);
- if (newUser) {
+ if (payload.adminCreated) {
+ SuccessToast("Account created successfully!");
+ yield call(approveUserList, { users: [payload.user_id]});
+ yield delay(1000);
+ yield loadUserlistSaga();
+ yield loadPendingUserListSaga();
+ } else if (newUser) {
SuccessToast("Account created successfully! Please login to continue.");
payload.navTo();
} else {
@@ -182,4 +199,5 @@ export default function* userSaga() {
yield takeLatest(GET_USER, getUserSaga);
yield takeLatest(REQUEST_RESET, requestResetSaga);
yield takeLatest(RESET_PASSWORD, resetPasswordSaga);
+ yield takeLatest(ADMIN_RESET_PASSWORD, adminResetPasswordSaga);
}