This commit is contained in:
Gaurav Kumar
2025-06-13 10:04:33 +05:30
parent 703f1256ff
commit 773b1f753b
89 changed files with 19379 additions and 9048 deletions

9374
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,15 +2,12 @@
"name": "log",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@mui/styles": "^5.16.4",
@@ -20,19 +17,36 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"ajv": "^6.12.6",
"ajv-keywords": "^3.5.2",
"axios": "^1.6.7",
"lucide-react": "^0.511.0",
"chart.js": "^4.4.9",
"mdb-react-ui-kit": "^7.1.0",
"moment": "^2.30.1",
"react": "^18.2.0",
"react-barcode": "^1.5.3",
"react-data-grid": "^7.0.0-beta.44",
"react-chartjs-2": "^5.3.0",
"react-data-grid": "^6.1.0",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^3.1.0",
"react-icons": "^5.2.1",
"react-grid-layout": "^1.5.1",
"react-hook-form": "^7.57.0",
"react-hot-toast": "^2.5.2",
"react-i18next": "^15.5.2",
"react-qr-code": "^2.0.14",
"react-router-dom": "^6.21.3",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
"react-toastify": "^11.0.5",
"schema-utils": "^3.3.0",
"web-vitals": "^2.1.4",
"webpack-dev-server": "^4.15.1",
"xlsx": "^0.18.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [

View File

@@ -1,8 +1,8 @@
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -10,7 +10,7 @@
content="Web site created using create-react-app"
/>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
@@ -25,7 +25,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>CloudnSure</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -0,0 +1,117 @@
import axios from 'axios';
import { getToken } from '../../src/utils/tokenService';
const BASE_URL = process.env.REACT_APP_API_BASE_URL;
const apiClient = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Add a request interceptor to include Authorization header
apiClient.interceptors.request.use(
(config) => {
const token = getToken();
if (token) {
console.log("token: ",token);
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Generic error handler function
const handleError = (error) => {
let errorMessage = 'An unknown error occurred';
console.error('Error Details:', error);
if (error.response) {
// HTTP errors
switch (error.response.status) {
case 401:
errorMessage = 'Unauthorized - Please login again.';
break;
case 403:
errorMessage = 'Forbidden - Access denied.';
break;
case 404:
errorMessage = 'Service not found.';
break;
case 408:
errorMessage = 'Request timed out.';
break;
case 500:
errorMessage = 'Internal server error.';
break;
default:
errorMessage = `Unexpected error: ${error.response.statusText || 'Server Error'}`;
}
} else if (error.request) {
// No response received
errorMessage = 'No response from server. Please check your connection.';
} else {
// Other errors
errorMessage = error.message || 'An unexpected error occurred.';
}
return Promise.reject(errorMessage); // Return error message as rejected promise
};
// Define the reusable methods
// const apiService = {
// get: (url, params) =>
// apiClient
// .get(url, { params: params || {} })
// .catch(handleError), // Attach error handler
// post: (url, body = {}) =>
// apiClient
// .post(url, body)
// .catch(handleError), // Attach error handler
// put: (url, body = {}) =>
// apiClient
// .put(url, body)
// .catch(handleError), // Attach error handler
// delete: (url) =>
// apiClient
// .delete(url)
// .catch(handleError), // Attach error handler
// };
const apiService = {
get: (url, params) =>
apiClient
.get(url, { params: params || {} })
.catch(handleError), // Attach error handler
post: (url, body = {}, options = {}) =>
apiClient
.post(url, body, options) // Pass options such as headers
.catch(handleError), // Attach error handler
put: (url, body = {}, options = {}) =>
apiClient
.put(url, body, options) // Pass options such as headers
.catch(handleError), // Attach error handler
delete: (url, options = {}) =>
apiClient
.delete(url, options) // Pass options such as headers
.catch(handleError), // Attach error handler
};
// Add at the bottom of APIService.js
export const getSubmenuItems = async (id) => {
const response = await apiService.get(`/api1/submenu1/${id}`);
return response.data;
};
export const addSubmenuItem = async (menuId, submenuData) => {
const response = await apiService.post(`/api1/menu/${menuId}/submenu`, submenuData);
return response.data;
};
export const updateMenuItem = (id, formData) => apiService.put(`/api1/submenu1/${id}`, formData);
export const deleteMenuItem = (id) => apiService.delete(`/api1/menu/${id}`);
export default apiService;

View File

@@ -0,0 +1,340 @@
// import axios from 'axios';
// class DashboardService {
// constructor() {
// this.baseURL = 'https://your-api-url'; // Set your base URL for API
// this.getAllURL = '/get_module_id';
// this.addDataURl = '/Savedata';
// this.deleteFieldURL = '/delete_by_header_id';
// this.getbyidURL = '/get_dashboard_headerbyid';
// this.editURL = '/update_Dashbord1_Line';
// this.updateURL = '/update_Dashbord1_Lineby_id';
// }
// // Get all dashboards
// getAllDash() {
// return axios.get(`${this.baseURL}/get_Dashboard_header`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching all dashboards:', error);
// throw error;
// });
// }
// // Get dashboards by module ID
// getAllByModuleId(module_id, page = 0, size = 1000) {
// return axios.get(`${this.baseURL}${this.getAllURL}`, {
// params: {
// page,
// size,
// module_id
// }
// })
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching dashboards by module ID:', error);
// throw error;
// });
// }
// // Create new dashboard
// create(data) {
// return axios.post(`${this.baseURL}${this.addDataURl}`, data)
// .then(response => response.data)
// .catch(error => {
// console.error('Error creating dashboard:', error);
// throw error;
// });
// }
// // Delete dashboard field
// deleteField(id) {
// return axios.delete(`${this.baseURL}${this.deleteFieldURL}/${id}`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error deleting dashboard field:', error);
// throw error;
// });
// }
// // Get dashboard by ID
// getById(id) {
// return axios.get(`${this.baseURL}${this.getbyidURL}/${id}`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching dashboard by ID:', error);
// throw error;
// });
// }
// // Add or Edit dashboard
// addToDB(line) {
// return axios.put(`${this.baseURL}${this.editURL}`, line)
// .then(response => response.data)
// .catch(error => {
// console.error('Error updating dashboard line:', error);
// throw error;
// });
// }
// // Update dashboard line data by ID
// updateLineData(id, line) {
// return axios.put(`${this.baseURL}${this.updateURL}/${id}`, line)
// .then(response => response.data)
// .catch(error => {
// console.error('Error updating dashboard line data:', error);
// throw error;
// });
// }
// // Get dashboard count by module ID
// getCount(moduleId) {
// return axios.get(`${this.baseURL}/get_dashboard/${moduleId}`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching dashboard count:', error);
// throw error;
// });
// }
// // Update dashboard header
// updateDash(dashboardHeader) {
// return axios.put(`${this.baseURL}/update_dashboard_header`, dashboardHeader)
// .then(response => response.data)
// .catch(error => {
// console.error('Error updating dashboard header:', error);
// throw error;
// });
// }
// // Schedule methods
// saveData(data) {
// return axios.post(`${this.baseURL}/DashboardSchedule/DashboardSchedule`, data)
// .then(response => response.data)
// .catch(error => {
// console.error('Error saving schedule data:', error);
// throw error;
// });
// }
// getDetails() {
// return axios.get(`${this.baseURL}/DashboardSchedule/DashboardSchedule`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching schedule details:', error);
// throw error;
// });
// }
// getDetailsById(id) {
// return axios.get(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching schedule details by ID:', error);
// throw error;
// });
// }
// deleteById(id) {
// return axios.delete(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`)
// .then(response => response.data)
// .catch(error => {
// console.error('Error deleting schedule by ID:', error);
// throw error;
// });
// }
// updateData(data, id) {
// return axios.put(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`, data)
// .then(response => response.data)
// .catch(error => {
// console.error('Error updating schedule data:', error);
// throw error;
// });
// }
// // Toggle functionality
// updateToggle(value) {
// // Logic for toggle update (e.g. update a state, or some other action)
// console.log('Toggle updated to:', value);
// }
// }
// export default new DashboardService();
import apiService from '../APIRequestService/APIServices';
class DashboardBuilderService {
constructor() {
this.getAllURL = '/get_module_id';
this.addDataURL = '/Savedata';
this.deleteFieldURL = '/delete_by_header_id';
this.getByIdURL = '/get_dashboard_headerbyid';
this.editURL = '/update_Dashbord1_Line';
this.updateURL = '/update_Dashbord1_Lineby_id';
}
// Get all dashboards
// getAllDash() {
// return apiService.get('/get_Dashboard_header')
// .then(response => response.data)
// .catch(error => {
// console.error('Error fetching all dashboards:', error);
// throw error;
// });
// }
async getAllDash() {
console.log('Entering getAllDash');
try {
const response = await apiService.get('/get_Dashboard_header');
console.log('Service Response:', response);
if (!response || !response.data) {
throw new Error('Unexpected API response format.');
}
return response.data;
} catch (error) {
console.error('Error fetching dashboards:', error);
throw new Error('Failed to fetch dashboards. Please try again later.');
}
}
// Get dashboards by module ID
getAllByModuleId(module_id, page = 0, size = 1000) {
return apiService.get(this.getAllURL, {
params: { page, size, module_id }
})
.then(response => response.data)
.catch(error => {
console.error('Error fetching dashboards by module ID:', error);
throw error;
});
}
// Create new dashboard
create(data) {
return apiService.post(this.addDataURL, data)
.then(response => response.data)
.catch(error => {
console.error('Error creating dashboard:', error);
throw error;
});
}
// Delete dashboard field
deleteField(id) {
return apiService.delete(`${this.deleteFieldURL}/${id}`)
.then(response => response.data)
.catch(error => {
console.error('Error deleting dashboard field:', error);
throw error;
});
}
// Get dashboard by ID
getById(id) {
return apiService.get(`${this.getByIdURL}/${id}`)
.then(response => response.data)
.catch(error => {
console.error('Error fetching dashboard by ID:', error);
throw error;
});
}
// Add or Edit dashboard
addToDB(line) {
return apiService.put(this.editURL, line)
.then(response => response.data)
.catch(error => {
console.error('Error updating dashboard line:', error);
throw error;
});
}
// Update dashboard line data by ID
updateLineData(id, line) {
return apiService.put(`${this.updateURL}/${id}`, line)
.then(response => response.data)
.catch(error => {
console.error('Error updating dashboard line data:', error);
throw error;
});
}
// Get dashboard count by module ID
getCount(moduleId) {
return apiService.get(`/get_dashboard/${moduleId}`)
.then(response => response.data)
.catch(error => {
console.error('Error fetching dashboard count:', error);
throw error;
});
}
// Update dashboard header
updateDash(dashboardHeader) {
return apiService.put('/update_dashboard_header', dashboardHeader)
.then(response => response.data)
.catch(error => {
console.error('Error updating dashboard header:', error);
throw error;
});
}
// Schedule methods
saveData(data) {
return apiService.post('/DashboardSchedule/DashboardSchedule', data)
.then(response => response.data)
.catch(error => {
console.error('Error saving schedule data:', error);
throw error;
});
}
getDetails() {
return apiService.get('/DashboardSchedule/DashboardSchedule')
.then(response => response.data)
.catch(error => {
console.error('Error fetching schedule details:', error);
throw error;
});
}
getDetailsById(id) {
return apiService.get(`/DashboardSchedule/DashboardSchedule/${id}`)
.then(response => response.data)
.catch(error => {
console.error('Error fetching schedule details by ID:', error);
throw error;
});
}
deleteById(id) {
return apiService.delete(`/DashboardSchedule/DashboardSchedule/${id}`)
.then(response => response.data)
.catch(error => {
console.error('Error deleting schedule by ID:', error);
throw error;
});
}
updateData(data, id) {
return apiService.put(`/DashboardSchedule/DashboardSchedule/${id}`, data)
.then(response => response.data)
.catch(error => {
console.error('Error updating schedule data:', error);
throw error;
});
}
// Toggle functionality
updateToggle(value) {
console.log('Toggle updated to:', value);
}
}
export default new DashboardBuilderService();

View File

@@ -0,0 +1,34 @@
import apiService from '../APIRequestService/APIServices';
// Exporting the function as named export
// export const getByUsrGrpId = async (usrgrp) => {
// try {
// const response = await apiService.get(`/api1/getusracces1/${usrgrp}`);
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// console.log(response.data);
// return response.data;
// } catch (error) {
// console.error('Error fetching user groups:', error);
// }
// }
export const getByUsrGrpId = async (usrgrp) => {
if (!usrgrp) {
console.error("Error: usrgrp is not defined or is null.");
return null;
}
try {
const response = await apiService.get(`/api1/getusracces1/${usrgrp}`);
if (response.status < 200 || response.status >= 300) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log("Fetched data successfully:", response.data);
return response.data; // Return the data if successfully fetched
} catch (error) {
console.error("Error fetching user groups:", error.message || error);
throw error; // Re-throw the error to handle it in the caller function
}
};

View File

@@ -0,0 +1,23 @@
import apiService from '../APIRequestService/APIServices';
export const getSysParameter = async (id) => {
try {
const response = await apiService.get(`/sysparam/getSysParams/${id}`);
return response.data; // Return only the data part of the response
} catch (error) {
throw error; // Let the error be handled by the calling component
}
}
export const addSysParameter = async (formdata) => {
try {
const response = await apiService.put(`/sysparam/updateSysParams/${1}`,formdata);
console.log("add response data ",response.data)
return response.data; // Return only the data part of the response
} catch (error) {
throw error; // Let the error be handled by the calling component
}
}

View File

@@ -0,0 +1,48 @@
import apiService from '../APIRequestService/APIServices';
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
export const getAllUsers = async () => {
try {
const response = await apiService.get(`${API_BASE_URL}/api/getAllAppUser`);
console.log(response.data);
return response;
} catch (error) {
console.error('Error fetching user groups:', error);
throw error; // Consider re-throwing the error for handling in components
}
}
export const createUser = async (data) => {
try {
const response = await apiService.post(`${API_BASE_URL}/api/addOneAppUser`, data);
console.log(response.data);
return response;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
};
export const updateUser = async (userData) => {
try {
const { userId, ...data } = userData;
const response = await apiService.put(`${API_BASE_URL}/api/updateAppUser/${userId}`, data);
console.log(response.data);
return response;
} catch (error) {
console.error('Error updating user:', error);
throw error;
}
};
export const deleteUser = async (id) => {
try {
const response = await apiService.delete(`${API_BASE_URL}/api/deleteAppUser/${id}`);
console.log(response.data);
return response;
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
};

View File

@@ -0,0 +1,153 @@
import apiService from '../../src/APIRequestService/APIServices';
export const fetchMenuItems = async () => {
console.log("applying fetch menu items");
try {
const response = await apiService.get('/api1/submenu1');
console.log("response", response);
if (response.status !== 200) {
throw new Error(`Error: ${response.status}`);
}
if (!response.data || !Array.isArray(response.data)) {
throw new Error('Unexpected data format, expected an array');
}
console.log('Fetched Menu Items:', response.data);
return response.data;
} catch (error) {
console.error('Error fetching menu items:', error);
throw error;
}
};
export const addMenuItem = async (formData) => {
try {
const payload = {
menuItemDesc: formData.menuItemDesc,
menuId: 0,
itemSeq: formData.itemSeq || 0,
moduleName: formData.moduleName || "",
main_menu_action_name: formData.main_menu_action_name || "#",
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
status: formData.status === "true"
};
console.log("Final payload:", payload);
const response = await apiService.post('/api1/Sec_menuDet/', payload);
return response.data;
} catch (error) {
console.error('Error adding menu item:', error);
throw error;
}
};
export const updateMenuItem = async (id, formData) => {
try {
const payload = {
menuItemId: Number(id),
menuItemDesc: formData.menuItemDesc,
menuId: formData.menuId || 0,
itemSeq: formData.itemSeq || 0,
moduleName: formData.moduleName || "",
main_menu_action_name: formData.main_menu_action_name || "#",
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
status: formData.status === "Enable" || formData.status === true
};
console.log("Updating menu item #"+id, payload);
const response = await apiService.post('/api1/Sec_menuDet/', payload);
console.log("Update successful:", response.data);
return response.data;
} catch (error) {
console.error('Update failed:', {
id: id,
error: error.response?.data || error.message
});
throw error;
}
};
export const deleteMenuItem = async (id) => {
try {
const response = await apiService.delete(`/api1/menu/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export const getSubmenuItems = async (id) => {
try {
const response = await apiService.get(`/api1/submenu1/${id}`);
console.log("get submenu response: ", response.data);
return response.data;
} catch (error) {
throw error;
}
}
export const addSubmenuItem = async (parentMenuId, formData) => {
try {
const payload = {
menuItemDesc: formData.menuItemDesc,
menuId: Number(parentMenuId),
itemSeq: formData.itemSeq || 0,
moduleName: formData.moduleName || "",
main_menu_action_name: formData.main_menu_action_name || "#",
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
status: formData.status === "Enable"
};
console.log("Adding submenu with payload:", payload);
const response = await apiService.post('/api1/Sec_menuDet/', payload);
return response.data;
} catch (error) {
console.error('Error adding submenu:', error);
throw error;
}
};
export const updateSubmenuItem = async (submenuId, formData) => {
const payload = {
menuItemId: Number(submenuId),
menuItemDesc: formData.menuItemDesc,
menuId: Number(formData.menuId),
itemSeq: formData.itemSeq || 0,
moduleName: formData.moduleName || "",
main_menu_action_name: formData.main_menu_action_name || "#",
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
status: formData.status === "Enable" || formData.status === true
};
try {
console.log("Updating submenu with payload:", payload);
const response = await apiService.post('/api1/Sec_menuDet/', payload);
return response.data;
} catch (error) {
console.error('Error updating submenu:', {
error: error.response?.data || error.message,
payload: payload
});
throw error;
}
};
export const deleteSubmenuItem = async (submenuId) => {
try {
console.log("Deleting submenu with ID:", submenuId);
const response = await apiService.post('/api1/Sec_menuDet/', {
menuItemId: Number(submenuId)
});
console.log("Delete successful:", response.data);
return response.data;
} catch (error) {
console.error('Error deleting submenu:', {
status: error.response?.status,
error: error.response?.data || error.message,
submenuId: submenuId
});
throw error;
}
};

View File

@@ -46,4 +46,3 @@
/* .login-container input[type="checkbox"] {
margin-right: 5px;
} */

View File

@@ -1,38 +1,115 @@
import React from "react";
import { Routes, Route } from "react-router-dom";
import { BrowserRouter as Router } from 'react-router-dom';
import { SystemParameterProvider } from './context/SystemParameterContext';
import {
BrowserRouter,
Routes,
Route,
Navigate
} from "react-router-dom";
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// Import components
import Login from "./components/Login/Login";
import Dashboard from "./components/Dashboard/dashboard";
import UserMaintance from "./components/Dashboard/UserMaintance";
import UserGroupMaintance from "./components/Dashboard/UserGroupMaintance/UserGroupMaintance";
import HomePage from "./components/Dashboard/HomePage";
import Setup from "./components/Dashboard/Setup";
import UserMaintenance from "./components/Dashboard/UserMaintance";
import UserGroupMaintenance from "./components/Dashboard/UserGroupMaintance/UserGroupMaintance";
import MenuMaintenance from "./components/Dashboard/MenuMaintance/MenuMaintance";
import MenuAccessControl from "./components/Dashboard/MenuAccessControl/MenuAccessControl";
import SystemParameters from "./components/Dashboard/SystemParameters/SystemParameters";
import AccessType from "./components/Dashboard/AccessType/AccessType";
import ApiRegistry from "./components/Dashboard/ApiRegistery/ApiRegistery";
import TokenRegistry from "./components/Dashboard/TokenRegistery/TokenRegistery";
import CodeExtension from "./components/Dashboard/Codeextension";
import Extension from "./components/Dashboard/Extension";
import DynamicTable from "./components/Dashboard/Dynamictable";
import Form from "./components/Dashboard/Form";
import ForgotPassword from "./components/Login/ForgotPassword";
import CreateAccount from "./components/Login/CreateAccount";
import DashboardBuilder from "./components/Dashboard/DashboardBuilder";
import Report from "./components/Dashboard/reports/Report";
import SequenceGenerator from "./components/Dashboard/document sequence/sequencegenerator";
import About from "./components/Dashboard/dropdown/about";
import Profile from "./components/Dashboard/dropdown/profile";
import DashboardRunner from "./components/dashboardnew/dashboardrunner/dashboardrunner";
import DashboardRunnerAll from "./components/dashboardnew/dashboardrunner/dashboardrunnerall";
import DashboardNewAll from "./components/dashboardnew/dashboardbuildernewall";
import DashboardNewAdd from "./components/dashboardnew/dashboardadd/dashboardbuilderadd";
import DashboardNewEdit from "./components/dashboardnew/editdashboard/editformdashboard";
import EditNewDash from "./components/dashboardnew/editdashboard/editdashboard";
import SubMenuMaintenance from "./components/Dashboard/sub menu/submenumaintanence";
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
const App = () => {
return (
<div>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/Dashboard" element={<Dashboard />} />
<Route path="/UserGroupMaintance" element={<UserGroupMaintance />} />
<Route path="/Dashboard/UserMaintance" element={<UserMaintance />} />
<Route path="/CodeExtension" element={<CodeExtension />} />
<Route path="/Dashboard/DashboardBuilder" element={<DashboardBuilder />} />
<Route path="/Extension" element={<Extension />} />
<Route path="/Dynamictable" element={<DynamicTable />} />
<Route path="/Form" element={<Form />} />
<Route path="/ForgotPassword" element={<ForgotPassword />} />
<Route path="/CreateAccount" element={<CreateAccount />} />
</Routes>
</Router>
</div>
<ThemeProvider theme={theme}>
<CssBaseline />
<SystemParameterProvider>
<BrowserRouter>
<ToastContainer
position="top-right"
autoClose={1500}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<Routes>
<Route path="/" element={<Login />} />
{/* Main dashboard route */}
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<HomePage />} />
{/* Setup section with all maintenance routes */}
<Route path="setup" element={<Setup />}>
<Route index element={<div>Select a setup option from the menu</div>} />
<Route path="user-maintenance" element={<UserMaintenance />} />
<Route path="user-group-maintenance" element={<UserGroupMaintenance />} />
<Route path="menu-maintenance" element={<MenuMaintenance />} />
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
<Route path="menu-access-control" element={<MenuAccessControl />} />
<Route path="system-parameters" element={<SystemParameters />} />
<Route path="access-type" element={<AccessType />} />
<Route path="api-registry" element={<ApiRegistry />} />
<Route path="token-registry" element={<TokenRegistry />} />
<Route path="document-sequence" element={<SequenceGenerator />} />
{/* Additional components */}
<Route path="code-extension" element={<CodeExtension />} />
<Route path="dynamic-table" element={<DynamicTable />} />
</Route>
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
<Route path="reports" element={<Report />} />
<Route path="about" element={<About />} />
<Route path="profile" element={<Profile />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</SystemParameterProvider>
</ThemeProvider>
);
};
export default App;
export default App;

View File

@@ -1,26 +1,26 @@
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error Boundary Caught an Error", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error Boundary Caught an Error", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;

View File

@@ -0,0 +1,469 @@
import React, { useState, useEffect } from "react";
import {
Button,
Container,
Checkbox,
Modal,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TextField,
FormControlLabel,
Typography,
Pagination,
IconButton,
InputAdornment,
Menu,
MenuItem,
Box,
Divider
} from "@mui/material";
import {
Edit as EditIcon,
Delete as DeleteIcon,
Add as AddIcon,
Menu as MenuIcon,
Close as CloseIcon,
Search as SearchIcon,
Check as CheckIcon,
Book as BookIcon
} from "@mui/icons-material";
import { toast } from "react-toastify";
import { CircularProgress } from "@mui/material";
const api =process.env.REACT_APP_API_BASE_URL;
function AccessTypeManagement() {
const [accessTypes, setAccessTypes] = useState([]);
const [showAddEditModal, setShowAddEditModal] = useState(false);
const [currentAccessType, setCurrentAccessType] = useState({
typeId: "",
typeName: "",
description: "",
isActive: false
});
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [visibleColumns, setVisibleColumns] = useState({
typeId: true,
typeName: true,
description: true,
isActive: true,
actions: true
});
const [loading, setLoading] = useState(true);
const [anchorEl, setAnchorEl] = useState(null);
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
const openColumnsMenu = (event) => {
setColumnsAnchorEl(event.currentTarget);
};
const closeColumnsMenu = () => {
setColumnsAnchorEl(null);
};
const openRecordsMenu = (event) => {
setAnchorEl(event.currentTarget);
};
const closeRecordsMenu = () => {
setAnchorEl(null);
};
useEffect(() => {
const fetchAccessTypes = async () => {
const token = localStorage.getItem("token");
const apiUrl = `${api}/token/access_type/Accesstype`;
try {
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setAccessTypes(data);
setLoading(false);
} catch (error) {
console.error("Error fetching access types:", error);
toast.error("Failed to fetch access types");
setLoading(false);
}
};
fetchAccessTypes();
}, []);
const toggleColumn = (column) => {
setVisibleColumns((prev) => ({
...prev,
[column]: !prev[column],
}));
};
const handleInputChange = (event) => {
const { name, value, checked, type } = event.target;
setCurrentAccessType((prev) => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
};
const handleSubmit = async (event) => {
event.preventDefault();
const token = localStorage.getItem("token");
const apiUrl = isEditing
? `${api}/access_type/Accesstype/${currentAccessType.id}`
: `${api}/access_type/Accesstype`;
try {
const method = isEditing ? "PUT" : "POST";
const response = await fetch(apiUrl, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(currentAccessType),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (isEditing) {
setAccessTypes(accessTypes.map(type =>
type.id === currentAccessType.id ? data : type
));
toast.success("Access type updated successfully!");
} else {
setAccessTypes([...accessTypes, data]);
toast.success("Access type added successfully!");
}
setShowAddEditModal(false);
} catch (error) {
console.error("Error submitting access type:", error);
toast.error("Failed to submit access type");
}
};
const handleDelete = async (id) => {
const token = localStorage.getItem("token");
const apiUrl = `${api}/access_type/Accesstype/${id}`;
try {
const response = await fetch(apiUrl, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
setAccessTypes(accessTypes.filter(type => type.id !== id));
toast.success("Access type deleted successfully!");
} catch (error) {
console.error("Error deleting access type:", error);
toast.error("Failed to delete access type");
}
};
const openModal = (type = { typeId: "", typeName: "", description: "", isActive: false }) => {
setIsEditing(!!type.id);
setCurrentAccessType(type);
setShowAddEditModal(true);
};
const handleClose = () => {
setShowAddEditModal(false);
};
const filteredAccessTypes = accessTypes.filter(item =>
item.typeName && item.typeName.toLowerCase().includes(searchQuery.toLowerCase())
);
const slicedAccessTypes = filteredAccessTypes.slice(
(currentPage - 1) * recordsPerPage,
currentPage * recordsPerPage
);
const totalPages = Math.ceil(filteredAccessTypes.length / recordsPerPage);
const handlePageChange = (event, value) => {
setCurrentPage(value);
};
const handleRecordsPerPageChange = (number) => {
setRecordsPerPage(number);
setCurrentPage(1);
closeRecordsMenu();
};
return (
<Container maxWidth="xl" sx={{ mt: 4 }}>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '200px' }}>
<CircularProgress />
</Box>
) : (
<>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3 }}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
Access Type
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<TextField
placeholder="Search"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
sx: {
borderRadius: '10px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
width: '100%',
maxWidth: '528px',
}
}}
sx={{ flexGrow: 1, maxWidth: '528px' }}
/>
<Box>
<IconButton onClick={() => openModal()} sx={{ mr: 2, color: 'primary.main' }}>
<AddIcon />
</IconButton>
<IconButton onClick={openColumnsMenu} sx={{ color: 'primary.main' }}>
<MenuIcon />
</IconButton>
</Box>
</Box>
<TableContainer component={Paper} sx={{ boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', mb: 3 }}>
<Table>
<TableHead sx={{ backgroundColor: '#f8f9fa' }}>
<TableRow>
{Object.entries(visibleColumns).map(([key, visible]) =>
visible && (
<TableCell key={key} sx={{ fontWeight: 'bold' }}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</TableCell>
)
)}
</TableRow>
</TableHead>
<TableBody>
{slicedAccessTypes.length === 0 ? (
<TableRow>
<TableCell colSpan={Object.keys(visibleColumns).filter(key => visibleColumns[key]).length} align="center">
No Data Available
</TableCell>
</TableRow>
) : (
slicedAccessTypes.map((type) => (
<TableRow key={type.id}>
{Object.entries(visibleColumns).map(([key, visible]) =>
visible && (
<TableCell key={key}>
{key === "actions" ? (
<Box>
<IconButton onClick={() => openModal(type)} sx={{ color: 'success.main', mr: 1 }}>
<EditIcon />
</IconButton>
<IconButton onClick={() => handleDelete(type.id)} sx={{ color: 'error.main' }}>
<DeleteIcon />
</IconButton>
</Box>
) : key === "isActive" ? (
<Typography
variant="body2"
sx={{
color: type.isActive ? 'success.main' : 'error.main',
backgroundColor: type.isActive ? 'rgba(0, 200, 83, 0.1)' : 'rgba(255, 0, 0, 0.1)',
px: 1,
py: 0.5,
borderRadius: 1,
display: 'inline-block'
}}
>
{type.isActive ? "Active" : "Inactive"}
</Typography>
) : (
type[key]
)}
</TableCell>
)
)}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
<Box>
<Button
variant="outlined"
onClick={openColumnsMenu}
startIcon={<MenuIcon />}
sx={{ mr: 2 }}
>
Manage Columns
</Button>
<Menu
anchorEl={columnsAnchorEl}
open={Boolean(columnsAnchorEl)}
onClose={closeColumnsMenu}
>
{Object.keys(visibleColumns).map((column) => (
<MenuItem key={column} onClick={() => toggleColumn(column)}>
<Checkbox checked={visibleColumns[column]} />
<Typography>
{column.charAt(0).toUpperCase() + column.slice(1)}
</Typography>
</MenuItem>
))}
</Menu>
<Button
variant="outlined"
onClick={openRecordsMenu}
startIcon={<BookIcon />}
>
Records
</Button>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeRecordsMenu}
>
{[5, 10, 20, 50].map((number) => (
<MenuItem
key={number}
onClick={() => handleRecordsPerPageChange(number)}
selected={recordsPerPage === number}
>
{number}
{recordsPerPage === number && <CheckIcon sx={{ ml: 1 }} />}
</MenuItem>
))}
</Menu>
</Box>
<Pagination
count={totalPages}
page={currentPage}
onChange={handlePageChange}
color="primary"
shape="rounded"
/>
</Box>
{/* Add/Edit Modal */}
<Modal
open={showAddEditModal}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
borderRadius: 1
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography id="modal-modal-title" variant="h6" component="h2">
{isEditing ? "Edit Access Type" : "Add Access Type"}
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Divider sx={{ mb: 3 }} />
<form onSubmit={handleSubmit}>
<TextField
fullWidth
label="Type Name"
name="typeName"
value={currentAccessType.typeName}
onChange={handleInputChange}
required
sx={{ mb: 2 }}
/>
<TextField
fullWidth
label="Description"
name="description"
value={currentAccessType.description}
onChange={handleInputChange}
sx={{ mb: 2 }}
/>
<FormControlLabel
control={
<Checkbox
name="isActive"
checked={currentAccessType.isActive}
onChange={handleInputChange}
/>
}
label="Active?"
sx={{ mb: 3 }}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
<Button variant="outlined" onClick={handleClose}>
Close
</Button>
<Button variant="contained" type="submit">
{isEditing ? "Update" : "Add"}
</Button>
</Box>
</form>
</Box>
</Modal>
</>
)}
</Container>
);
}
export default AccessTypeManagement;

View File

@@ -1,138 +1,468 @@
import React, { useState, useEffect, useRef } from "react";
import { Box, Button } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer className="flex justify-between p-2 bg-gray-200">
<Button
onClick={handleGoToPage1}
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600"
>
Go to page 1
</Button>
<Button
onClick={handleModal}
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600"
>
Add item
</Button>
</GridToolbarContainer>
);
}
function ApiRegistery() {
const [menuItems, setMenuItems] = useState([]);
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
// eslint-disable-next-line
const [isModalOpen, setIsModalOpen] = useState(false);
const apiRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
const response = await fetch(
`${api}/Api_registery_header/Api_registery_header`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
setMenuItems(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (menuItemId) => {
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
};
const columns = [
{
field: "id",
headerName: "ID",
width: 300,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "table_name",
headerName: "Table Name",
width: 350,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div className="relative">
<div
className="cursor-pointer"
onClick={() => handleThreeDotsClick(row.id)}
>
<BsThreeDotsVertical /> {/* Using react-icons */}
</div>
{selectedMenuItem === row.id && (
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
Edit
</button>
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
Delete
</button>
</div>
)}
</div>
),
},
];
return (
<div className="flex justify-center mt-5">
<Box className="w-full max-w-7xl">
<div className="bg-white p-4 rounded shadow-md">
<h1 className="text-2xl font-bold mb-4 text-white bg-gray-400 p-3">
API Registry
</h1>
<DataGrid
rows={menuItems}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleModal={() => setIsModalOpen(true)}
/>
),
}}
pageSize={10}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
/>
</div>
</Box>
</div>
);
}
export default ApiRegistery;
import React, { useState, useEffect } from "react";
import {
Button,
IconButton,
Modal,
TextField,
Checkbox,
FormControlLabel,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Pagination,
Menu,
MenuItem,
Box,
Typography,
InputAdornment,
Tooltip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
FormControl,
Select,
InputLabel,
CircularProgress,
} from "@mui/material";
import {
Edit as EditIcon,
Delete as DeleteIcon,
Add as AddIcon,
Menu as MenuIcon,
Close as CloseIcon,
Search as SearchIcon,
} from "@mui/icons-material";
import { styled } from "@mui/material/styles";
import { toast } from "react-toastify";
import { fetchRegistry, addRegistry, updateRegistry, deleteRegistry } from "./Apiregistryapi";
const StyledTableRow = styled(TableRow)(({ theme }) => ({
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
'&:hover': {
backgroundColor: theme.palette.action.selected,
},
}));
const APIRegistry = () => {
const [apiEntries, setApiEntries] = useState([]);
const [openModal, setOpenModal] = useState(false);
const [currentApiEntry, setCurrentApiEntry] = useState({
table_name: "",
isActive: false,
});
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [visibleColumns, setVisibleColumns] = useState({
id: true,
table_name: true,
createdAt: true,
updatedAt: true,
isActive: true,
actions: true,
});
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
const [recordsAnchorEl, setRecordsAnchorEl] = useState(null);
const columnsMenuOpen = Boolean(columnsAnchorEl);
const recordsMenuOpen = Boolean(recordsAnchorEl);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const data = await fetchRegistry();
setApiEntries(data || []);
} catch (error) {
toast.error("Failed to fetch API entries");
console.error("Error loading data:", error);
} finally {
setLoading(false);
}
};
loadData();
}, []);
const handleColumnsMenuClick = (event) => {
setColumnsAnchorEl(event.currentTarget);
};
const handleRecordsMenuClick = (event) => {
setRecordsAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setColumnsAnchorEl(null);
setRecordsAnchorEl(null);
};
const toggleColumn = (column) => {
setVisibleColumns((prev) => ({
...prev,
[column]: !prev[column],
}));
};
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setCurrentApiEntry((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}));
};
const handleSearch = (query) => {
setSearchQuery(query);
setCurrentPage(1);
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
setLoading(true);
let response;
if (isEditing) {
response = await updateRegistry(currentApiEntry.id, currentApiEntry);
toast.success("API entry updated successfully!");
} else {
response = await addRegistry(currentApiEntry);
toast.success("API entry added successfully!");
}
// Refresh the data
const data = await fetchRegistry();
setApiEntries(data || []);
setOpenModal(false);
} catch (error) {
toast.error(error.message || "Failed to save API entry");
console.error("Save error:", error);
} finally {
setLoading(false);
}
};
const handleOpenModal = (entry = {
table_name: "",
isActive: false,
}) => {
setIsEditing(!!entry.id);
setCurrentApiEntry(entry);
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const handleDelete = async (id) => {
try {
setLoading(true);
await deleteRegistry(id);
const data = await fetchRegistry();
setApiEntries(data || []);
toast.success("API entry deleted successfully!");
} catch (error) {
toast.error(error.message || "Failed to delete API entry");
console.error("Delete error:", error);
} finally {
setLoading(false);
}
};
const handleRecordsPerPageChange = (value) => {
setRecordsPerPage(value);
setCurrentPage(1);
handleMenuClose();
};
// Filter and pagination logic
const filteredAPIEntries = apiEntries.filter(item =>
item.table_name?.toLowerCase().includes(searchQuery.toLowerCase())
);
const totalPages = Math.ceil(filteredAPIEntries.length / recordsPerPage);
const slicedAPIEntries = filteredAPIEntries.slice(
(currentPage - 1) * recordsPerPage,
currentPage * recordsPerPage
);
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="80vh">
<CircularProgress />
</Box>
);
}
return (
<Box sx={{ marginTop: "11rem", padding: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
API Registry
</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, alignItems: 'center', my: 3, gap: 2 }}>
<TextField
placeholder="Search"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
sx={{
width: { xs: '100%', md: '60%' },
maxWidth: '528px',
'& .MuiOutlinedInput-root': {
borderRadius: '10px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
},
}}
/>
<Box sx={{ display: 'flex', gap: 2 }}>
<Tooltip title="Add API Entry">
<IconButton onClick={() => handleOpenModal()} color="primary">
<AddIcon />
</IconButton>
</Tooltip>
<Tooltip title="Menu">
<IconButton onClick={handleColumnsMenuClick} color="primary">
<MenuIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
<TableContainer component={Paper} sx={{ boxShadow: 3, mb: 3 }}>
<Table>
<TableHead sx={{ bgcolor: 'primary.main' }}>
<TableRow>
{Object.entries(visibleColumns)
.filter(([_, visible]) => visible)
.map(([key]) => (
<TableCell key={key} sx={{ color: 'white', fontWeight: 'bold' }}>
{key.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{slicedAPIEntries.length === 0 ? (
<TableRow>
<TableCell
colSpan={Object.values(visibleColumns).filter(Boolean).length}
align="center"
>
No Data Available
</TableCell>
</TableRow>
) : (
slicedAPIEntries.map((entry) => (
<StyledTableRow key={entry.id}>
{Object.entries(visibleColumns)
.filter(([_, visible]) => visible)
.map(([key]) => (
<TableCell key={key}>
{key === "actions" ? (
<Box sx={{ display: 'flex', gap: 1 }}>
<Tooltip title="Edit">
<IconButton
onClick={() => handleOpenModal(entry)}
color="primary"
>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
onClick={() => handleDelete(entry.id)}
color="error"
>
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
) : key === "isActive" ? (
<Typography
sx={{
color: entry.isActive ? 'success.main' : 'error.main',
bgcolor: entry.isActive ? 'success.light' : 'error.light',
px: 1,
py: 0.5,
borderRadius: 1,
display: 'inline-block',
}}
>
{entry.isActive ? "Active" : "Inactive"}
</Typography>
) : (
entry[key]
)}
</TableCell>
))}
</StyledTableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 3 }}>
<Box>
<Button
variant="outlined"
onClick={handleColumnsMenuClick}
startIcon={<MenuIcon />}
>
Manage Columns
</Button>
<Menu
anchorEl={columnsAnchorEl}
open={columnsMenuOpen}
onClose={handleMenuClose}
>
{Object.entries(visibleColumns).map(([column, visible]) => (
<MenuItem key={column} onClick={() => toggleColumn(column)}>
<FormControlLabel
control={
<Checkbox
checked={visible}
onChange={() => toggleColumn(column)}
/>
}
label={column.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}
/>
</MenuItem>
))}
</Menu>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography variant="body2">
Rows per page:
</Typography>
<FormControl size="small" sx={{ minWidth: 80 }}>
<Select
value={recordsPerPage}
onChange={(e) => handleRecordsPerPageChange(e.target.value)}
displayEmpty
inputProps={{ 'aria-label': 'Without label' }}
>
{[5, 10, 20, 50].map((number) => (
<MenuItem key={number} value={number}>
{number}
</MenuItem>
))}
</Select>
</FormControl>
<Pagination
count={totalPages}
page={currentPage}
onChange={(_, value) => setCurrentPage(value)}
color="primary"
showFirstButton
showLastButton
/>
</Box>
</Box>
<Dialog open={openModal} onClose={handleCloseModal} fullWidth maxWidth="sm">
<DialogTitle>
{isEditing ? "Edit API Entry" : "Add API Entry"}
<IconButton
aria-label="close"
onClick={handleCloseModal}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
<TextField
margin="normal"
fullWidth
label="Table Name"
name="table_name"
value={currentApiEntry.table_name}
onChange={handleInputChange}
required
error={!currentApiEntry.table_name}
helperText={!currentApiEntry.table_name ? "Table name is required" : ""}
/>
<TextField
margin="normal"
fullWidth
label="Created At"
name="createdAt"
type="datetime-local"
value={currentApiEntry.createdAt || ''}
onChange={handleInputChange}
InputLabelProps={{
shrink: true,
}}
/>
<TextField
margin="normal"
fullWidth
label="Updated At"
name="updatedAt"
type="datetime-local"
value={currentApiEntry.updatedAt || ''}
onChange={handleInputChange}
InputLabelProps={{
shrink: true,
}}
/>
<FormControlLabel
control={
<Checkbox
name="isActive"
checked={currentApiEntry.isActive}
onChange={handleInputChange}
/>
}
label="Active?"
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseModal}>Cancel</Button>
<Button
onClick={handleSubmit}
variant="contained"
color="primary"
disabled={!currentApiEntry.table_name}
>
{isEditing ? "Update" : "Add"}
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default APIRegistry;

View File

@@ -0,0 +1,54 @@
import apiService from '../../../APIRequestService/APIServices';
export const fetchRegistry = async () => {
try {
const response = await apiService.get('/Api_registery_header/Api_registery_header');
if (response.data && Array.isArray(response.data)) {
return response.data;
}
throw new Error('Invalid response format');
} catch (error) {
console.error("Error in fetchRegistry:", error);
throw error;
}
};
export const addRegistry = async (payload) => {
try {
const response = await apiService.post('/Api_registery_header/Api_registery_header', {
table_name: payload.table_name,
createdAt: payload.createdAt || new Date().toISOString(),
updatedAt: payload.updatedAt || new Date().toISOString(),
isActive: payload.isActive || false
});
return response.data;
} catch (error) {
console.error("Error in addRegistry:", error);
throw error;
}
};
export const updateRegistry = async (id, payload) => {
try {
const response = await apiService.put(`/Api_registery_header/Api_registery_header/${id}`, {
table_name: payload.table_name,
createdAt: payload.createdAt,
updatedAt: new Date().toISOString(), // Always update the updatedAt timestamp
isActive: payload.isActive
});
return response.data;
} catch (error) {
console.error("Error in updateRegistry:", error);
throw error;
}
};
export const deleteRegistry = async (id) => {
try {
const response = await apiService.delete(`/Api_registery_header/Api_registery_header/${id}`);
return response.data;
} catch (error) {
console.error("Error in deleteRegistry:", error);
throw error;
}
};

View File

@@ -0,0 +1,142 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser, faUsers, faUtensils, faLock, faCogs, faKey } from '@fortawesome/free-solid-svg-icons';
import { Card, CardContent, Typography, Box } from '@mui/material';
const CardItem = ({ title, content, icon, path }) => {
const navigate = useNavigate();
return (
<Card
sx={{
width: 300,
height: 150,
m: 2,
cursor: 'pointer',
transition: 'transform 0.2s',
'&:hover': {
transform: 'scale(1.05)',
boxShadow: '10px 10px 10px 10px rgba(25,118,210,0.15)'
},
boxShadow: '10px 10px 10px 10px rgba(0,0,0,0.05)',
backgroundColor: '#fff',
borderRadius: '8px',
display: 'flex',
alignItems: 'center'
}}
onClick={() => navigate(path)}
>
<CardContent sx={{
display: 'flex',
alignItems: 'center',
width: '100%',
padding: '16px !important'
}}>
<Box sx={{
backgroundColor: '#1976d2',
borderRadius: '50%',
width: 50,
height: 50,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginRight: 2,
color: 'white'
}}>
<FontAwesomeIcon icon={icon} style={{ fontSize: '1.2rem' }} />
</Box>
<Box>
<Typography variant="subtitle1" component="div" fontWeight="bold">
{title}
</Typography>
<Typography variant="body2" color="text.secondary">
{content}
</Typography>
</Box>
</CardContent>
</Card>
);
};
const CardList = () => {
const cards = [
{
title: "User Maintenance",
content: "Manage users",
icon: faUser,
path: "/dashboard/setup/user-maintenance"
},
{
title: "User Group Maintenance",
content: "Manage user groups",
icon: faUsers,
path: "/dashboard/setup/user-group-maintenance"
},
{
title: "Menu Maintenance",
content: "Manage menus",
icon: faUtensils,
path: "/dashboard/setup/menu-maintenance"
},
{
title: "Menu Access Control",
content: "Control menu access",
icon: faLock,
path: "/dashboard/setup/menu-access-control"
},
{
title: "System Parameters",
content: "Configure system parameters",
icon: faCogs,
path: "/dashboard/setup/system-parameters"
},
{
title: "Access Type",
content: "Manage access types",
icon: faKey,
path: "/dashboard/setup/access-type"
},
{
title: "Document Sequence",
content: "generate sequence",
icon: faCogs,
path: "/dashboard/setup/document-sequence"
},
{
title: "Api Registry",
content: "Manage APIs",
icon: faUser,
path: "/dashboard/setup/api-registry"
},
{
title: "Token Registry",
content: "Manage tokens",
icon: faKey,
path: "/dashboard/setup/token-registry"
},
];
return (
<Box sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
padding: '2rem',
//backgroundColor: '#f5f5f5',
// minHeight: '100vh'
}}>
{cards.map((card, index) => (
<CardItem
key={index}
title={card.title}
content={card.content}
icon={card.icon}
path={card.path}
/>
))}
</Box>
);
};
export default CardList;

View File

@@ -1,64 +1,64 @@
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background-color: #fff;
padding: 20px;
border-radius: 8px;
}
.modal-content {
margin-top: 20px;
}
.close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
font-size: 20px;
}
.close:hover {
color: red;
}
.popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.popup-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
width: 400px; /* Adjust width as needed */
}
.close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
font-size: 20px;
}
.close:hover {
color: red;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background-color: #fff;
padding: 20px;
border-radius: 8px;
}
.modal-content {
margin-top: 20px;
}
.close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
font-size: 20px;
}
.close:hover {
color: red;
}
.popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.popup-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
width: 400px; /* Adjust width as needed */
}
.close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
font-size: 20px;
}
.close:hover {
color: red;
}

View File

@@ -1,301 +1,302 @@
import React, { useState, useEffect } from "react";
import {
Box,
Button,
Modal,
TextField,
Typography,
FormControl,
FormControlLabel,
Checkbox,
Radio,
RadioGroup,
Autocomplete,
} from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { FaEllipsisV } from "react-icons/fa"; // Importing react-icons instead of Font Awesome
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
import { Link } from "react-router-dom";
import Extension from "./Extension";
function CustomToolbar({ handleModal }) {
return (
<GridToolbarContainer className="flex justify-between p-2 bg-gray-200">
<Button
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600"
onClick={handleModal}
>
+
</Button>
</GridToolbarContainer>
);
}
function CodeExtension() {
const [menuItems, setMenuItems] = useState([]);
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [formData, setFormData] = useState({
name: "",
email: "",
testing: "",
dataType: "",
});
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("authToken"); // Get token from local storage
try {
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/extension`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
setMenuItems(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (menuItemId) => {
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
};
const handleModalOpen = () => {
setIsModalOpen(true);
};
const handleModalClose = () => {
setIsModalOpen(false);
setFormData({
name: "",
email: "",
testing: "",
dataType: "",
});
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleFormSubmit = (submittedDataType) => {
setFormData({ ...formData, dataType: submittedDataType });
handleModalOpen();
};
const columns = [
{ field: "goto", headerName: "Goto", width: 200 },
{ field: "field_name", headerName: "Field Name", width: 250 },
{ field: "mapping", headerName: "Mapping", width: 200 },
{ field: "data_type", headerName: "Data Type", width: 200 },
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div className="relative">
<div
className="cursor-pointer"
onClick={() => handleThreeDotsClick(row.id)}
>
<FaEllipsisV /> {/* Using react-icons instead of Font Awesome */}
</div>
{selectedMenuItem === row.id && (
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
{/* Implement your actions buttons here */}
</div>
)}
</div>
),
},
];
const renderInputField = () => {
switch (formData.dataType) {
case "date":
return (
<TextField
label="Date"
name="date"
type="date"
value={formData.date}
onChange={handleChange}
fullWidth
className="mt-2"
/>
);
case "textfield":
return (
<TextField
label="Text Field"
name="textfield"
value={formData.textfield}
onChange={handleChange}
fullWidth
className="mt-2"
/>
);
case "longtext":
return (
<TextField
label="Long Text"
name="longtext"
value={formData.longtext}
onChange={handleChange}
multiline
rows={4}
fullWidth
className="mt-2"
/>
);
case "checkbox":
return (
<FormControlLabel
className="mt-2"
control={
<Checkbox
checked={formData.checkbox || false}
onChange={(e) =>
setFormData({ ...formData, checkbox: e.target.checked })
}
/>
}
label="Checkbox"
/>
);
case "radiobutton":
return (
<FormControl component="fieldset" className="mt-2">
<RadioGroup
name="radiobutton"
value={formData.radiobutton || ""}
onChange={(e) =>
setFormData({ ...formData, radiobutton: e.target.value })
}
>
<FormControlLabel
value="option1"
control={<Radio />}
label="Option 1"
/>
<FormControlLabel
value="option2"
control={<Radio />}
label="Option 2"
/>
</RadioGroup>
</FormControl>
);
case "autocomplete":
return (
<Autocomplete
options={["Option 1", "Option 2", "Option 3"]}
renderInput={(params) => (
<TextField {...params} label="Autocomplete" />
)}
value={formData.autocomplete || ""}
onChange={(e, newValue) =>
setFormData({ ...formData, autocomplete: newValue })
}
fullWidth
className="mt-2"
/>
);
default:
return null;
}
};
return (
<>
<Box className="fixed top-0 left-0 w-full z-10 bg-white shadow">
{/* Your header content here */}
</Box>
<Box className="flex justify-center items-center min-h-screen bg-gray-100 py-4">
<Box className="w-full max-w-6xl bg-white p-4 rounded shadow-md">
<Typography
variant="h4"
className="text-center mb-4 text-3xl text-white bg-gray-400 p-3"
>
Token Registry
</Typography>
<div className="bg-gray-50 p-2 rounded shadow-inner">
<DataGrid
rows={menuItems}
columns={columns}
pageSize={10}
components={{
Toolbar: () => <CustomToolbar handleModal={handleModalOpen} />,
}}
className="data-grid"
/>
</div>
<Modal open={isModalOpen} onClose={handleModalClose} centered>
<Box className="w-full max-w-lg bg-white p-4 rounded shadow-md fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<Extension onSubmit={handleFormSubmit} />
<Typography variant="h5" className="flex items-center mb-4">
<Link to="/Extension">
<AirplanemodeActiveIcon className="mr-2" />
</Link>
Add Item
</Typography>
<form
onSubmit={(e) => {
e.preventDefault();
handleFormSubmit(formData.dataType);
}}
>
<div className="mb-2">
<TextField
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
fullWidth
/>
</div>
<div className="mb-2">
<TextField
label="Email"
name="email"
value={formData.email}
onChange={handleChange}
fullWidth
/>
</div>
<div className="mb-2">
<TextField
label="Testing"
name="testing"
value={formData.testing}
onChange={handleChange}
fullWidth
/>
</div>
{renderInputField()}
<div className="mt-4">
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
>
Submit
</Button>
</div>
</form>
</Box>
</Modal>
</Box>
</Box>
</>
);
}
export default CodeExtension;
import React, { useState, useEffect, useRef } from "react";
import {
Box,
Button,
Modal,
TextField,
Typography,
FormControl,
FormControlLabel,
Checkbox,
Radio,
RadioGroup,
Autocomplete,
} from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
import { Link } from "react-router-dom";
import Extension from "./Extension";
function CustomToolbar({ handleModal }) {
return (
<GridToolbarContainer className="flex justify-between p-2 bg-gray-200">
<Button
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600"
onClick={handleModal}
>
+
</Button>
</GridToolbarContainer>
);
}
function CodeExtension() {
const [menuItems, setMenuItems] = useState([]);
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [formData, setFormData] = useState({
name: "",
email: "",
testing: "",
dataType: "",
});
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/extension`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
setMenuItems(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (menuItemId) => {
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
};
const handleModalOpen = () => {
setIsModalOpen(true);
};
const handleModalClose = () => {
setIsModalOpen(false);
setFormData({
name: "",
email: "",
testing: "",
dataType: "",
});
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleFormSubmit = (submittedDataType) => {
setFormData({ ...formData, dataType: submittedDataType });
handleModalOpen();
};
const columns = [
{ field: "goto", headerName: "Goto", width: 200 },
{ field: "field_name", headerName: "Field Name", width: 250 },
{ field: "mapping", headerName: "Mapping", width: 200 },
{ field: "data_type", headerName: "Data Type", width: 200 },
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div className="relative">
<div
className="cursor-pointer"
onClick={() => handleThreeDotsClick(row.id)}
>
<FontAwesomeIcon icon={faEllipsisV} />
</div>
{selectedMenuItem === row.id && (
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
{/* Implement your actions buttons here */}
</div>
)}
</div>
),
},
];
const renderInputField = () => {
switch (formData.dataType) {
case "date":
return (
<TextField
label="Date"
name="date"
type="date"
value={formData.date}
onChange={handleChange}
fullWidth
className="mt-2"
/>
);
case "textfield":
return (
<TextField
label="Text Field"
name="textfield"
value={formData.textfield}
onChange={handleChange}
fullWidth
className="mt-2"
/>
);
case "longtext":
return (
<TextField
label="Long Text"
name="longtext"
value={formData.longtext}
onChange={handleChange}
multiline
rows={4}
fullWidth
className="mt-2"
/>
);
case "checkbox":
return (
<FormControlLabel
className="mt-2"
control={
<Checkbox
checked={formData.checkbox || false}
onChange={(e) =>
setFormData({ ...formData, checkbox: e.target.checked })
}
/>
}
label="Checkbox"
/>
);
case "radiobutton":
return (
<FormControl component="fieldset" className="mt-2">
<RadioGroup
name="radiobutton"
value={formData.radiobutton || ""}
onChange={(e) =>
setFormData({ ...formData, radiobutton: e.target.value })
}
>
<FormControlLabel
value="option1"
control={<Radio />}
label="Option 1"
/>
<FormControlLabel
value="option2"
control={<Radio />}
label="Option 2"
/>
</RadioGroup>
</FormControl>
);
case "autocomplete":
return (
<Autocomplete
options={["Option 1", "Option 2", "Option 3"]}
renderInput={(params) => (
<TextField {...params} label="Autocomplete" />
)}
value={formData.autocomplete || ""}
onChange={(e, newValue) =>
setFormData({ ...formData, autocomplete: newValue })
}
fullWidth
className="mt-2"
/>
);
default:
return null;
}
};
return (
<>
<Box className="fixed top-0 left-0 w-full z-10 bg-white shadow">
{/* Your header content here */}
</Box>
<Box className="flex justify-center items-center min-h-screen bg-gray-100 py-4">
<Box className="w-full max-w-6xl bg-white p-4 rounded shadow-md">
<Typography
variant="h4"
className="text-center mb-4 text-3xl text-white bg-gray-400 p-3"
>
Token Registry
</Typography>
<div className="bg-gray-50 p-2 rounded shadow-inner">
<DataGrid
rows={menuItems}
columns={columns}
pageSize={10}
components={{
Toolbar: () => <CustomToolbar handleModal={handleModalOpen} />,
}}
className="data-grid"
/>
</div>
<Modal open={isModalOpen} onClose={handleModalClose} centered>
<Box className="w-full max-w-lg bg-white p-4 rounded shadow-md fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<Extension onSubmit={handleFormSubmit} />
<Typography variant="h5" className="flex items-center mb-4">
<Link to="/Extension">
<AirplanemodeActiveIcon className="mr-2" />
</Link>
Add Item
</Typography>
<form
onSubmit={(e) => {
e.preventDefault();
handleFormSubmit(formData.dataType);
}}
>
<div className="mb-2">
<TextField
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
fullWidth
/>
</div>
<div className="mb-2">
<TextField
label="Email"
name="email"
value={formData.email}
onChange={handleChange}
fullWidth
/>
</div>
<div className="mb-2">
<TextField
label="Testing"
name="testing"
value={formData.testing}
onChange={handleChange}
fullWidth
/>
</div>
{renderInputField()}
<div className="mt-4">
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
>
Submit
</Button>
</div>
</form>
</Box>
</Modal>
</Box>
</Box>
</>
);
}
export default CodeExtension;

View File

@@ -1,161 +1,161 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { useLocation, useNavigate } from "react-router-dom";
import {
Button,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Box,
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import BuildIcon from "@mui/icons-material/Build";
import AddIcon from "@mui/icons-material/Add";
// Define the API base URL using the environment variable
const api = process.env.REACT_APP_API_BASE_URL;
const DynamicTable = () => {
const [forms, setForms] = useState([]);
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (location.state && location.state.formData) {
setForms((prevForms) => [...prevForms, location.state.formData]);
} else {
fetchForms();
}
}, [location.state]);
const fetchForms = async () => {
const token = localStorage.getItem("authToken"); // Get token from local storage
try {
const response = await axios.get(`${api}/api/form_setup`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (Array.isArray(response.data)) {
setForms(response.data);
} else {
console.error("Unexpected response format:", response.data);
setForms([]);
}
} catch (error) {
console.error("Error fetching data:", error);
setForms([]);
}
};
const handleDelete = async (id) => {
const token = localStorage.getItem("authToken"); // Get token from local storage
try {
await axios.delete(`${api}/api/form_setup/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
fetchForms();
} catch (error) {
console.error("Error deleting form:", error);
}
};
const handleBuild = async (id) => {
const token = localStorage.getItem("authToken"); // Get token from local storage
try {
await axios.post(
`${api}/api/dynamic_form_build`,
{ id },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
} catch (error) {
console.error("Error building form:", error);
}
};
const handleAdd = () => {
navigate("/form");
};
return (
<Box className="p-5 bg-gray-100 min-h-screen">
<Typography
variant="h4"
gutterBottom
className="text-center text-white bg-gray-700 text-3xl p-3 mb-5 rounded"
>
Dynamic Form
</Typography>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={handleAdd}
className="mb-5 bg-blue-500 hover:bg-blue-600"
>
Add
</Button>
<TableContainer component={Paper} className="overflow-x-auto">
<Table>
<TableHead className="bg-gray-300 text-black">
<TableRow>
<TableCell>Go To</TableCell>
<TableCell>Form Name</TableCell>
<TableCell>Form Description</TableCell>
<TableCell>Related To</TableCell>
<TableCell>Page Event</TableCell>
<TableCell>Button Caption</TableCell>
<TableCell>Go To Form</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{forms.map((form, index) => (
<TableRow key={index}>
<TableCell>
<Button
variant="outlined"
startIcon={<BuildIcon />}
onClick={() => handleBuild(form.id)}
className="border-blue-500 text-blue-500 hover:bg-blue-100"
>
Build
</Button>
</TableCell>
<TableCell>{form.formName}</TableCell>
<TableCell>{form.formDescription}</TableCell>
<TableCell>{form.relatedTo}</TableCell>
<TableCell>{form.pageEvent}</TableCell>
<TableCell>{form.buttonCaption}</TableCell>
<TableCell>{form.goToForm}</TableCell>
<TableCell>
<IconButton
color="secondary"
onClick={() => handleDelete(form.id)}
className="text-red-500 hover:text-red-700"
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
};
export default DynamicTable;
import React, { useState, useEffect } from "react";
import axios from "axios";
import { useLocation, useNavigate } from "react-router-dom";
import {
Button,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Box,
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import BuildIcon from "@mui/icons-material/Build";
import AddIcon from "@mui/icons-material/Add";
// Define the API base URL using the environment variable
const api = process.env.REACT_APP_API_BASE_URL;
const DynamicTable = () => {
const [forms, setForms] = useState([]);
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (location.state && location.state.formData) {
setForms((prevForms) => [...prevForms, location.state.formData]);
} else {
fetchForms();
}
}, [location.state]);
const fetchForms = async () => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
const response = await axios.get(`${api}/api/form_setup`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (Array.isArray(response.data)) {
setForms(response.data);
} else {
console.error("Unexpected response format:", response.data);
setForms([]);
}
} catch (error) {
console.error("Error fetching data:", error);
setForms([]);
}
};
const handleDelete = async (id) => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
await axios.delete(`${api}/api/form_setup/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
fetchForms();
} catch (error) {
console.error("Error deleting form:", error);
}
};
const handleBuild = async (id) => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
await axios.post(
`${api}/api/dynamic_form_build`,
{ id },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
} catch (error) {
console.error("Error building form:", error);
}
};
const handleAdd = () => {
navigate("/form");
};
return (
<Box className="p-5 bg-gray-100 min-h-screen">
<Typography
variant="h4"
gutterBottom
className="text-center text-white bg-gray-700 text-3xl p-3 mb-5 rounded"
>
Dynamic Form
</Typography>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={handleAdd}
className="mb-5 bg-blue-500 hover:bg-blue-600"
>
Add
</Button>
<TableContainer component={Paper} className="overflow-x-auto">
<Table>
<TableHead className="bg-gray-300 text-black">
<TableRow>
<TableCell>Go To</TableCell>
<TableCell>Form Name</TableCell>
<TableCell>Form Description</TableCell>
<TableCell>Related To</TableCell>
<TableCell>Page Event</TableCell>
<TableCell>Button Caption</TableCell>
<TableCell>Go To Form</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{forms.map((form, index) => (
<TableRow key={index}>
<TableCell>
<Button
variant="outlined"
startIcon={<BuildIcon />}
onClick={() => handleBuild(form.id)}
className="border-blue-500 text-blue-500 hover:bg-blue-100"
>
Build
</Button>
</TableCell>
<TableCell>{form.formName}</TableCell>
<TableCell>{form.formDescription}</TableCell>
<TableCell>{form.relatedTo}</TableCell>
<TableCell>{form.pageEvent}</TableCell>
<TableCell>{form.buttonCaption}</TableCell>
<TableCell>{form.goToForm}</TableCell>
<TableCell>
<IconButton
color="secondary"
onClick={() => handleDelete(form.id)}
className="text-red-500 hover:text-red-700"
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
};
export default DynamicTable;

View File

@@ -1,93 +1,93 @@
import React, { useState } from 'react';
import { TextField, Button, Typography, Select, MenuItem } from '@mui/material';
import { useNavigate } from 'react-router-dom';
const Extension = ({ onSubmit }) => {
const [formData, setFormData] = useState({
type: '',
fieldName: '',
mapping: '',
dataType: ''
});
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (typeof onSubmit === 'function') {
onSubmit(formData.dataType);
}
// Navigate to CodeExtension page with the form data
navigate('/CodeExtension', { state: { formData } });
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<Typography variant="h4" gutterBottom>Add Item</Typography>
<form onSubmit={handleSubmit}>
<div>
<Select
label="Type"
name="type"
value={formData.type}
onChange={handleChange}
fullWidth
required
>
<MenuItem value="Header">Header</MenuItem>
<MenuItem value="Line">Line</MenuItem>
</Select>
</div>
<div>
<TextField
label="Field Name"
name="fieldName"
value={formData.fieldName}
onChange={handleChange}
fullWidth
required
/>
</div>
<div>
<Select
label="Mapping"
name="mapping"
value={formData.mapping}
onChange={handleChange}
fullWidth
required
>
{[...Array(15).keys()].map(num => (
<MenuItem key={num + 1} value={`EXTN${num + 1}`}>{`EXTN${num + 1}`}</MenuItem>
))}
</Select>
</div>
<div>
<Select
label="Data Type"
name="dataType"
value={formData.dataType}
onChange={handleChange}
fullWidth
required
>
<MenuItem value="textfield">Textfield</MenuItem>
<MenuItem value="longtext">Longtext</MenuItem>
<MenuItem value="date">Date</MenuItem>
<MenuItem value="checkbox">Checkbox</MenuItem>
<MenuItem value="radiobutton">Radiobutton</MenuItem>
<MenuItem value="autocomplete">Autocomplete</MenuItem>
</Select>
</div>
<Button type="submit" variant="contained" sx={{ mt: 2 }}>Submit</Button>
</form>
</div>
);
};
export default Extension;
import React, { useState } from 'react';
import { TextField, Button, Typography, Select, MenuItem } from '@mui/material';
import { useNavigate } from 'react-router-dom';
const Extension = ({ onSubmit }) => {
const [formData, setFormData] = useState({
type: '',
fieldName: '',
mapping: '',
dataType: ''
});
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (typeof onSubmit === 'function') {
onSubmit(formData.dataType);
}
// Navigate to CodeExtension page with the form data
navigate('/Codeextension', { state: { formData } });
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<Typography variant="h4" gutterBottom>Add Item</Typography>
<form onSubmit={handleSubmit}>
<div>
<Select
label="Type"
name="type"
value={formData.type}
onChange={handleChange}
fullWidth
required
>
<MenuItem value="Header">Header</MenuItem>
<MenuItem value="Line">Line</MenuItem>
</Select>
</div>
<div>
<TextField
label="Field Name"
name="fieldName"
value={formData.fieldName}
onChange={handleChange}
fullWidth
required
/>
</div>
<div>
<Select
label="Mapping"
name="mapping"
value={formData.mapping}
onChange={handleChange}
fullWidth
required
>
{[...Array(15).keys()].map(num => (
<MenuItem key={num + 1} value={`EXTN${num + 1}`}>{`EXTN${num + 1}`}</MenuItem>
))}
</Select>
</div>
<div>
<Select
label="Data Type"
name="dataType"
value={formData.dataType}
onChange={handleChange}
fullWidth
required
>
<MenuItem value="textfield">Textfield</MenuItem>
<MenuItem value="longtext">Longtext</MenuItem>
<MenuItem value="date">Date</MenuItem>
<MenuItem value="checkbox">Checkbox</MenuItem>
<MenuItem value="radiobutton">Radiobutton</MenuItem>
<MenuItem value="autocomplete">Autocomplete</MenuItem>
</Select>
</div>
<Button type="submit" variant="contained" sx={{ mt: 2 }}>Submit</Button>
</form>
</div>
);
};
export default Extension;

View File

@@ -1,217 +1,217 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
function Form() {
const [components, setComponents] = useState([
{
id: uuidv4(),
label: "",
type: "",
mapping: "",
readonly: false,
values: "",
},
]);
const [formDetails, setFormDetails] = useState({
formName: "",
formDescription: "",
relatedTo: "",
pageEvent: "",
buttonName: "",
});
const navigate = useNavigate();
const handleFormChange = (e) => {
const { name, value } = e.target;
setFormDetails({ ...formDetails, [name]: value });
};
const handleComponentChange = (index, field, value) => {
const updatedComponents = components.map((component, i) =>
i === index ? { ...component, [field]: value } : component
);
setComponents(updatedComponents);
};
const addComponent = () => {
setComponents([
...components,
{
id: uuidv4(),
label: "",
type: "",
mapping: "",
readonly: false,
values: "",
},
]);
};
const removeComponent = (index) => {
setComponents(components.filter((_, i) => i !== index));
};
const handleSubmit = (e) => {
e.preventDefault();
const formData = { ...formDetails, components };
navigate("/Dynamictable", { state: { formData } }); // Navigate to DynamicTable with formData
};
return (
<div className="p-5 min-h-screen">
<h1 className="text-3xl font-bold text-center text-white bg-gray-400 mb-8 p-3">
Dynamic Form Setup
</h1>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="flex flex-col">
<label className="text-gray-700">Form Name</label>
<input
type="text"
name="formName"
value={formDetails.formName}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Form Description</label>
<input
type="text"
name="formDescription"
value={formDetails.formDescription}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Related To</label>
<select
name="relatedTo"
value={formDetails.relatedTo}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
>
<option value="">
<em>None</em>
</option>
<option value="Menu">Menu</option>
<option value="Related to">Related to</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="flex flex-col">
<label className="text-gray-700">Page Event</label>
<select
name="pageEvent"
value={formDetails.pageEvent}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
>
<option value="Onclick">Onclick</option>
<option value="Onblur">Onblur</option>
</select>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Button Name</label>
<input
type="text"
name="buttonName"
value={formDetails.buttonName}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
</div>
<h2 className="text-2xl font-semibold text-gray-700 mt-8">
Component Details
</h2>
{components.map((component, index) => (
<div
key={component.id}
className="grid grid-cols-1 md:grid-cols-5 gap-4 items-center mt-4"
>
<input
type="text"
placeholder="Label"
value={component.label}
onChange={(e) =>
handleComponentChange(index, "label", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<select
value={component.type}
onChange={(e) =>
handleComponentChange(index, "type", e.target.value)
}
className="p-2 border rounded col-span-1"
>
<option value="">
<em>None</em>
</option>
<option value="textfield">TextField</option>
<option value="checkbox">Checkbox</option>
<option value="select">Select</option>
</select>
<input
type="text"
placeholder="Mapping"
value={component.mapping}
onChange={(e) =>
handleComponentChange(index, "mapping", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<div className="flex items-center">
<input
type="checkbox"
checked={component.readonly}
onChange={(e) =>
handleComponentChange(index, "readonly", e.target.checked)
}
className="mr-2"
/>
<label>Readonly</label>
</div>
<input
type="text"
placeholder="Enter Values"
value={component.values}
onChange={(e) =>
handleComponentChange(index, "values", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<button
type="button"
onClick={() => removeComponent(index)}
className="text-red-500 hover:text-red-700"
>
{/* <DeleteIcon /> */}
</button>
</div>
))}
<div className="flex justify-between mt-6">
<button
type="button"
onClick={addComponent}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Add Component
</button>
<button
type="submit"
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
Submit
</button>
</div>
</form>
</div>
);
}
export default Form;
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
function DynamicForm() {
const [components, setComponents] = useState([
{
id: uuidv4(),
label: "",
type: "",
mapping: "",
readonly: false,
values: "",
},
]);
const [formDetails, setFormDetails] = useState({
formName: "",
formDescription: "",
relatedTo: "",
pageEvent: "",
buttonName: "",
});
const navigate = useNavigate();
const handleFormChange = (e) => {
const { name, value } = e.target;
setFormDetails({ ...formDetails, [name]: value });
};
const handleComponentChange = (index, field, value) => {
const updatedComponents = components.map((component, i) =>
i === index ? { ...component, [field]: value } : component
);
setComponents(updatedComponents);
};
const addComponent = () => {
setComponents([
...components,
{
id: uuidv4(),
label: "",
type: "",
mapping: "",
readonly: false,
values: "",
},
]);
};
const removeComponent = (index) => {
setComponents(components.filter((_, i) => i !== index));
};
const handleSubmit = (e) => {
e.preventDefault();
const formData = { ...formDetails, components };
navigate("/Dynamictable", { state: { formData } }); // Navigate to DynamicTable with formData
};
return (
<div className="p-5 min-h-screen">
<h1 className="text-3xl font-bold text-center text-white bg-gray-400 mb-8 p-3">
Dynamic Form Setup
</h1>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="flex flex-col">
<label className="text-gray-700">Form Name</label>
<input
type="text"
name="formName"
value={formDetails.formName}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Form Description</label>
<input
type="text"
name="formDescription"
value={formDetails.formDescription}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Related To</label>
<select
name="relatedTo"
value={formDetails.relatedTo}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
>
<option value="">
<em>None</em>
</option>
<option value="Menu">Menu</option>
<option value="Related to">Related to</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="flex flex-col">
<label className="text-gray-700">Page Event</label>
<select
name="pageEvent"
value={formDetails.pageEvent}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
>
<option value="Onclick">Onclick</option>
<option value="Onblur">Onblur</option>
</select>
</div>
<div className="flex flex-col">
<label className="text-gray-700">Button Name</label>
<input
type="text"
name="buttonName"
value={formDetails.buttonName}
onChange={handleFormChange}
className="mt-1 p-2 border rounded"
/>
</div>
</div>
<h2 className="text-2xl font-semibold text-gray-700 mt-8">
Component Details
</h2>
{components.map((component, index) => (
<div
key={component.id}
className="grid grid-cols-1 md:grid-cols-5 gap-4 items-center mt-4"
>
<input
type="text"
placeholder="Label"
value={component.label}
onChange={(e) =>
handleComponentChange(index, "label", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<select
value={component.type}
onChange={(e) =>
handleComponentChange(index, "type", e.target.value)
}
className="p-2 border rounded col-span-1"
>
<option value="">
<em>None</em>
</option>
<option value="textfield">TextField</option>
<option value="checkbox">Checkbox</option>
<option value="select">Select</option>
</select>
<input
type="text"
placeholder="Mapping"
value={component.mapping}
onChange={(e) =>
handleComponentChange(index, "mapping", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<div className="flex items-center">
<input
type="checkbox"
checked={component.readonly}
onChange={(e) =>
handleComponentChange(index, "readonly", e.target.checked)
}
className="mr-2"
/>
<label>Readonly</label>
</div>
<input
type="text"
placeholder="Enter Values"
value={component.values}
onChange={(e) =>
handleComponentChange(index, "values", e.target.value)
}
className="p-2 border rounded col-span-1"
/>
<button
type="button"
onClick={() => removeComponent(index)}
className="text-red-500 hover:text-red-700"
>
{/* <DeleteIcon /> */}
</button>
</div>
))}
<div className="flex justify-between mt-6">
<button
type="button"
onClick={addComponent}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Add Component
</button>
<button
type="submit"
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
Submit
</button>
</div>
</form>
</div>
);
}
export default DynamicForm;

View File

@@ -1,50 +1,50 @@
/* HomePage.css */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
.heading {
font-size: 24px;
margin-bottom: 20px;
}
.card-container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap; /* Allow cards to wrap to the next line */
margin-top: 20px;
}
.card {
background-color: #9ee2f5;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 30%; /* Adjust card width for larger screens */
margin: 10px;
}
.chart-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
@media (max-width: 768px) {
.card {
width: 45%; /* Adjust card width for screens up to 768px */
}
}
@media (max-width: 480px) {
.card {
width: 100%; /* Make cards occupy full width on screens up to 480px */
}
}
/* HomePage.css */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
.heading {
font-size: 24px;
margin-bottom: 20px;
}
.card-container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap; /* Allow cards to wrap to the next line */
margin-top: 20px;
}
.card {
background-color: #9ee2f5;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 30%; /* Adjust card width for larger screens */
margin: 10px;
}
.chart-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
@media (max-width: 768px) {
.card {
width: 45%; /* Adjust card width for screens up to 768px */
}
}
@media (max-width: 480px) {
.card {
width: 100%; /* Make cards occupy full width on screens up to 480px */
}
}

View File

@@ -1,113 +1,37 @@
// HomePage.js
import React from 'react';
import { FaUsers, FaCog, FaChartBar, FaShieldAlt } from 'react-icons/fa';
const StatCard = ({ icon: Icon, title, value, color }) => (
<div className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-all duration-200">
<div className="flex items-center space-x-4">
<div className={`p-3 rounded-lg ${color}`}>
<Icon className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-gray-500 text-sm font-medium">{title}</h3>
<p className="text-2xl font-bold text-gray-800">{value}</p>
</div>
</div>
</div>
);
const HomePage = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold text-gray-800">Welcome Back!</h1>
<div className="text-sm text-gray-500">Last updated: {new Date().toLocaleDateString()}</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<StatCard
icon={FaUsers}
title="Total Users"
value="1,234"
color="bg-gradient-to-r from-purple-500 to-indigo-500"
/>
<StatCard
icon={FaCog}
title="Active Systems"
value="12"
color="bg-gradient-to-r from-blue-500 to-cyan-500"
/>
<StatCard
icon={FaChartBar}
title="Reports Generated"
value="456"
color="bg-gradient-to-r from-indigo-500 to-purple-500"
/>
<StatCard
icon={FaShieldAlt}
title="Security Score"
value="98%"
color="bg-gradient-to-r from-green-500 to-emerald-500"
/>
</div>
{/* Recent Activity */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Recent Activity</h2>
<div className="space-y-4">
{[1, 2, 3].map((item) => (
<div key={item} className="flex items-center space-x-4 p-4 rounded-lg hover:bg-gray-50 transition-colors">
<div className="w-2 h-2 rounded-full bg-purple-500"></div>
<div className="flex-1">
<p className="text-gray-800">System update completed</p>
<p className="text-sm text-gray-500">2 hours ago</p>
</div>
</div>
))}
</div>
</div>
{/* Quick Actions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl shadow-lg p-6 text-white">
<h2 className="text-xl font-semibold mb-4">Quick Actions</h2>
<div className="space-y-3">
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Generate Report
</button>
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Add New User
</button>
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
System Settings
</button>
</div>
</div>
<div className="bg-gradient-to-br from-blue-500 to-cyan-600 rounded-xl shadow-lg p-6 text-white">
<h2 className="text-xl font-semibold mb-4">System Status</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span>CPU Usage</span>
<span className="font-medium">45%</span>
</div>
<div className="w-full bg-white/20 rounded-full h-2">
<div className="bg-white h-2 rounded-full" style={{ width: '45%' }}></div>
</div>
<div className="flex items-center justify-between">
<span>Memory Usage</span>
<span className="font-medium">62%</span>
</div>
<div className="w-full bg-white/20 rounded-full h-2">
<div className="bg-white h-2 rounded-full" style={{ width: '62%' }}></div>
</div>
</div>
</div>
</div>
</div>
);
};
export default HomePage;
// HomePage.js
import React from 'react';
import { BarChart } from '@mui/x-charts/BarChart'; // Import BarChart component
const Card = ({ index }) => {
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
<h3 className="text-lg font-semibold mb-2">INDEX {index}</h3>
<p className="text-gray-600">{index}.</p>
</div>
);
};
const HomePage = () => {
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4">
<h2 className="text-3xl font-semibold text-gray-700 mb-6">Welcome to the Dashboard!</h2>
<div className="flex flex-wrap justify-center">
<Card index={1} />
<Card index={2} />
<Card index={3} />
</div>
<div className="w-full mt-8 flex justify-center">
{/* Add BarChart component */}
<BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
width={700}
height={400}
/>
</div>
</div>
);
};
export default HomePage;

View File

@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}

View File

@@ -1,178 +1,643 @@
import React, { useState, useEffect, useRef } from "react";
import { Box, Button } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
import "./MenuMaintance.css";
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer>
<Button onClick={handleGoToPage1}>Go to page 1</Button>
<Button onClick={handleModal}>+</Button>
</GridToolbarContainer>
);
}
function MenuMaintenance() {
const [menuItems, setMenuItems] = useState([]);
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const apiRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("authToken");
try {
const response = await fetch(`${api}/api1/submenu1`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Flatten the nested subMenus array
const flattenedData = data.flatMap((menuItem) => [
menuItem,
...menuItem.subMenus,
]);
// Set unique IDs for each menu item
const menuItemsWithIds = flattenedData.map((menuItem, index) => ({
...menuItem,
id: index + 1,
}));
setMenuItems(menuItemsWithIds);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (menuItemId) => {
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
};
const columns = [
{
field: "menuItemId",
headerName: "Menu Item ID",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "menuItemDesc",
headerName: "Menu Item Description",
width: 250,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "moduleName",
headerName: "Module Name",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "main_menu_action_name",
headerName: "Main Menu Action",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "main_menu_icon_name",
headerName: "Main Menu Icon",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div className="relative">
<div
className="three-dots"
onClick={() => handleThreeDotsClick(row.menuItemId)}
>
<BsThreeDotsVertical
className="cursor-pointer text-gray-800 hover:text-gray-600"
/>
</div>
{selectedMenuItem === row.menuItemId && (
<div className="absolute bg-white border border-gray-200 shadow-lg p-4 mt-2 rounded-lg">
{/* Implement your actions buttons here */}
</div>
)}
</div>
),
},
];
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="text-3xl text-center text-black mb-3 bg-slate-400">
Menu Maintenance
</div>
<Box
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2"
sx={{ height: 500, width: "100%" }}
>
<DataGrid
rows={menuItems}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleThreeDotsClick={handleThreeDotsClick}
handleModal={() => setIsModalOpen(true)}
/>
),
}}
pageSize={10}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
sx={{
"& .MuiDataGrid-columnHeaders": {
backgroundColor: "rgba(107, 114, 128, 0.5)", // Tailwind CSS bg-gray-400 with opacity
},
"& .MuiDataGrid-columnHeaderTitle": {
fontWeight: "bold",
},
}}
className="border border-gray-200 shadow-lg rounded-lg bg-gray-400"
/>
</Box>
{/* Your modals and other components */}
{isModalOpen && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-8 rounded-lg shadow-lg">
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
{/* Modal content here */}
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
</div>
</div>
)}
</div>
);
}
export default MenuMaintenance;
import React, { useState, useEffect } from "react";
import {
Box,
Button,
Container,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
IconButton,
Tooltip,
Menu,
MenuItem,
Checkbox,
Pagination,
Select,
FormControl,
InputLabel,
Modal,
FormGroup,
FormControlLabel,
Switch,
CircularProgress,
Chip,
Grid,
InputAdornment
} from "@mui/material";
import {
Edit as EditIcon,
Delete as DeleteIcon,
Add as AddIcon,
Menu as MenuIcon,
ArrowBack as ArrowBackIcon,
Close as CloseIcon,
Download as DownloadIcon,
Upload as UploadIcon,
InsertDriveFile as ExcelIcon,
Search as SearchIcon,
CheckCircle as CheckCircleIcon
} from "@mui/icons-material";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";
import * as XLSX from "xlsx";
import {
fetchMenuItems,
addMenuItem,
updateMenuItem,
deleteMenuItem,
getSubmenuItems,
addSubmenuItem,
updateSubmenuItem,
deleteSubmenuItem
} from "../../../ApiServices/menumaintananceapi";
function MenuMaintenance() {
const [menuItems, setMenuItems] = useState([]);
const [subMenuItems, setSubMenuItems] = useState([]);
const [showAddEditPopup, setShowAddEditPopup] = useState(false);
const [currentMenuItem, setCurrentMenuItem] = useState({
menuItemDesc: "",
menuId: 0,
itemSeq: "",
moduleName: "",
main_menu_action_name: "",
main_menu_icon_name: "",
status: "true"
});
const [isEditing, setIsEditing] = useState(false);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [visibleColumns, setVisibleColumns] = useState({
menuItemDesc: true,
menu_id: true,
itemSeq: true,
moduleName: true,
main_menu_action_name: true,
main_menu_icon_name: true,
status: true,
});
const [isSubMenu, setIsSubMenu] = useState(false);
const [parentMenuItemId, setParentMenuItemId] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(true);
const [showModal, setShowModal] = useState(false);
const [error, setError] = useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const [pageAnchorEl, setPageAnchorEl] = useState(null);
const navigate = useNavigate();
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 3000);
}, []);
useEffect(() => {
const fetchData = async () => {
try {
console.log("Fetching Menu Items...");
const data = await fetchMenuItems();
setMenuItems(data);
console.log("Fetched Menu Items:", data);
toast.success("Menu items fetched successfully!");
} catch (error) {
console.error("Fetching error:", error);
setError(error.message);
toast.error("Error fetching menu items.");
}
};
fetchData();
}, []);
const toggleColumn = (column) => {
setVisibleColumns((prev) => ({
...prev,
[column]: !prev[column],
}));
};
const openAddEditPopup = (menuItem = {}) => {
setIsEditing(!!menuItem.menuItemId);
setCurrentMenuItem(menuItem);
setParentMenuItemId(isSubMenu ? menuItem.menuItemId : null);
setShowAddEditPopup(true);
};
const handleOpenModal = () => setShowModal(true);
const handleCloseModal = () => setShowModal(false);
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) {
console.log("Selected file:", file.name);
}
};
const exportToExcel = () => {
const worksheet = XLSX.utils.json_to_sheet([]);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "UserDetails");
XLSX.writeFile(workbook, "MenuMaintenance.xlsx");
};
const handleInputChange = (event) => {
const { name, value } = event.target;
const processedValue = name === "status" ? value === "true" : value;
setCurrentMenuItem((prev) => ({ ...prev, [name]: processedValue }));
};
const handleSearch = (query) => {
setSearchQuery(query);
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
if (isEditing) {
const updatedItem = await updateMenuItem(currentMenuItem.menuItemId, currentMenuItem);
setMenuItems(menuItems.map(item =>
item.menuItemId === currentMenuItem.menuItemId ? updatedItem : item
));
toast.success("Menu item updated successfully!");
} else {
const addedItem = await addMenuItem(currentMenuItem);
setMenuItems([...menuItems, addedItem]);
toast.success("Menu item added successfully!");
}
setShowAddEditPopup(false);
} catch (error) {
toast.error(`Operation failed: ${error.message}`);
console.error("Submit error:", error);
}
};
const handleDelete = async (menuItemId) => {
if (isSubMenu) {
setSubMenuItems(
subMenuItems.filter((item) => item.menuItemId !== menuItemId)
);
} else {
await deleteMenuItem(menuItemId);
setMenuItems(menuItems.filter((item) => item.menuItemId !== menuItemId));
}
toast.success("Menu item deleted successfully!");
};
const handleRecordsPerPageChange = (number) => {
setRecordsPerPage(number);
setPageAnchorEl(null);
};
const handleSubMenuClick = (menuItemId) => {
navigate(`/dashboard/setup/sub-menu-maintenance/${menuItemId}`);
fetchSubMenuItems(menuItemId);
};
const fetchSubMenuItems = async (menuItemId) => {
try {
const data = await getSubmenuItems(menuItemId);
console.log("Fetched Sub-Menu Items:", data);
setSubMenuItems(data);
setIsSubMenu(true);
setParentMenuItemId(menuItemId);
toast.success("Sub-menu items fetched successfully!");
} catch (error) {
console.error("Error fetching sub-menu items:", error);
toast.error("Failed to fetch sub-menu items.");
}
};
const handleMainMenuClick = (menuId) => {
console.log("Selected Menu ID:", menuId);
};
const handleBackToMainMenu = () => {
setIsSubMenu(false);
setSubMenuItems([]);
setParentMenuItemId(null);
};
const totalPages = Math.ceil(menuItems.length / recordsPerPage);
const handlePageChange = (event, pageNumber) => {
setCurrentPage(pageNumber);
};
const handleClose = () => {
setShowAddEditPopup(false);
};
const slicedMenus = (isSubMenu ? subMenuItems : menuItems)
.filter(
(menuItem) =>
menuItem.menuItemDesc &&
menuItem.menuItemDesc.toLowerCase().includes(searchQuery.toLowerCase())
)
.slice((currentPage - 1) * recordsPerPage, currentPage * recordsPerPage);
const handleMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handlePageMenuOpen = (event) => {
setPageAnchorEl(event.currentTarget);
};
const handlePageMenuClose = () => {
setPageAnchorEl(null);
};
return (
<Box sx={{ mt: -2 }}>
{loading ? (
<Box display="flex" justifyContent="center" alignItems="center" height="80vh">
<CircularProgress />
</Box>
) : (
<Container maxWidth="xl" sx={{ mt: 5 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
Menu Maintenance
</Typography>
</Box>
<Grid container spacing={2} alignItems="center" my={3}>
<Grid item xs={12} md={8} lg={6}>
<TextField
fullWidth
placeholder="Search"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: '#fff' }} />
</InputAdornment>
),
sx: {
backgroundColor: '#fff',
borderRadius: '10px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
'& .MuiOutlinedInput-notchedOutline': {
border: 'none'
}
}
}}
sx={{
maxWidth: 528
}}
/>
</Grid>
<Grid item xs={12} md={4} lg={6} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
{isSubMenu && (
<IconButton onClick={handleBackToMainMenu} sx={{ mr: 2 }}>
<ArrowBackIcon sx={{ color: '#747264', fontSize: '1.5rem' }} />
</IconButton>
)}
<Tooltip title="Add Menu">
<IconButton onClick={() => openAddEditPopup()} sx={{ mr: 2 }}>
<AddIcon sx={{ color: '#747264', fontSize: '1.5rem' }} />
</IconButton>
</Tooltip>
<Tooltip title="Download template">
<IconButton sx={{ mr: 2 }}>
<DownloadIcon sx={{ color: '#747264', fontSize: '1.5rem' }} />
</IconButton>
</Tooltip>
<Tooltip title="Import">
<IconButton onClick={handleOpenModal} sx={{ mr: 2 }}>
<UploadIcon sx={{ color: '#747264', fontSize: '1.5rem' }} />
</IconButton>
</Tooltip>
<Tooltip title="XLSX">
<IconButton onClick={exportToExcel} sx={{ mr: 2 }}>
<ExcelIcon sx={{ color: '#747264', fontSize: '1.5rem' }} />
</IconButton>
</Tooltip>
</Grid>
</Grid>
<Modal open={showModal} onClose={handleCloseModal}>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
borderRadius: 1
}}>
<Typography variant="h6" component="h2" mb={2}>
Import File
</Typography>
<FormControl fullWidth>
<InputLabel>Select a file to import:</InputLabel>
<input
type="file"
accept=".xlsx, .xls, .csv"
onChange={handleFileChange}
style={{ marginBottom: 16 }}
/>
</FormControl>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
<Button onClick={handleCloseModal} sx={{ mr: 2 }}>Cancel</Button>
<Button variant="contained" onClick={handleCloseModal}>Upload</Button>
</Box>
</Box>
</Modal>
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
<Table>
<TableHead sx={{ backgroundColor: '#f8f9fa' }}>
<TableRow>
<TableCell>No</TableCell>
{Object.entries(visibleColumns).map(
([key, visible]) =>
visible && (
<TableCell key={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</TableCell>
)
)}
<TableCell>Actions</TableCell>
{!isSubMenu && <TableCell>Sub-Menu</TableCell>}
</TableRow>
</TableHead>
<TableBody>
{slicedMenus.length === 0 ? (
<TableRow>
<TableCell
colSpan={
1 +
Object.keys(visibleColumns).filter(
(key) => visibleColumns[key]
).length +
(isSubMenu ? 1 : 2)
}
align="center"
>
No menu items found. Please add new items.
</TableCell>
</TableRow>
) : (
slicedMenus.map((menuItem, index) => (
<TableRow
key={`${menuItem.menuItemId}-${index}`}
hover
onClick={() => handleMainMenuClick(menuItem.menuItemId)}
>
<TableCell>{index + 1}</TableCell>
{Object.keys(visibleColumns).map(
(key) =>
visibleColumns[key] && (
<TableCell key={key}>
{key === "status" ? (
<Chip
label={menuItem.status ? "Enabled" : "Disabled"}
color={menuItem.status ? "success" : "error"}
size="small"
/>
) : (
menuItem[key]
)}
</TableCell>
)
)}
<TableCell align="center">
<IconButton
onClick={() => openAddEditPopup(menuItem)}
sx={{ color: 'green' }}
>
<EditIcon />
</IconButton>
<IconButton
onClick={() => handleDelete(menuItem.menuItemId)}
sx={{ color: 'error.main' }}
>
<DeleteIcon />
</IconButton>
</TableCell>
{!isSubMenu && (
<TableCell align="center">
<IconButton
onClick={() => handleSubMenuClick(menuItem.menuItemId)}
sx={{ color: '#0B4C6A' }}
>
<MenuIcon />
</IconButton>
</TableCell>
)}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<Grid container spacing={2} mt={4}>
<Grid item xs={12} md={6}>
<Button
variant="outlined"
onClick={handleMenuOpen}
sx={{ textTransform: 'none' }}
>
Manage Columns
</Button>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
{Object.keys(visibleColumns).map((column) => (
<MenuItem key={column} onClick={() => toggleColumn(column)}>
<Checkbox checked={visibleColumns[column]} />
{column.charAt(0).toUpperCase() + column.slice(1).toLowerCase()}
</MenuItem>
))}
</Menu>
</Grid>
<Grid item xs={12} md={6} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="outlined"
onClick={handlePageMenuOpen}
sx={{ textTransform: 'none' }}
>
<ExcelIcon />
</Button>
<Menu
anchorEl={pageAnchorEl}
open={Boolean(pageAnchorEl)}
onClose={handlePageMenuClose}
>
{[1, 5, 10, 20, 50].map((number) => (
<MenuItem
key={number}
onClick={() => handleRecordsPerPageChange(number)}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<span>{number}</span>
{recordsPerPage === number && (
<CheckCircleIcon sx={{ ml: 'auto', color: 'success.main' }} />
)}
</Box>
</MenuItem>
))}
</Menu>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Pagination
count={totalPages}
page={currentPage}
onChange={handlePageChange}
color="primary"
/>
</Box>
<Modal
open={showAddEditPopup}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '80%',
maxWidth: 800,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
borderRadius: 1
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography id="modal-modal-title" variant="h6" component="h2">
{isEditing ? "Edit Menu Item" : "Add New Menu Item"}
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Box component="form" onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
fullWidth
label="Menu ID*"
name="menuId"
value={0}
InputProps={{
readOnly: true,
}}
variant="standard"
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label="Menu Item Name*"
name="menuItemDesc"
value={currentMenuItem.menuItemDesc || ""}
onChange={handleInputChange}
required
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label="Sequence"
name="itemSeq"
type="number"
value={currentMenuItem.itemSeq || ""}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label="Module Name"
name="moduleName"
value={currentMenuItem.moduleName || ""}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label="Menu Action Link"
name="main_menu_action_name"
value={currentMenuItem.main_menu_action_name || ""}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label="Menu Icon Name"
name="main_menu_icon_name"
value={currentMenuItem.main_menu_icon_name || ""}
onChange={handleInputChange}
placeholder="e.g., fa-home"
/>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Status</InputLabel>
<Select
name="status"
value={currentMenuItem.status ? "true" : "false"}
onChange={handleInputChange}
label="Status"
>
<MenuItem value="true">Enable</MenuItem>
<MenuItem value="false">Disable</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3 }}>
<Button onClick={handleClose} sx={{ mr: 2 }}>Cancel</Button>
<Button type="submit" variant="contained">
{isEditing ? "UPDATE" : "ADD"}
</Button>
</Box>
</Box>
</Box>
</Modal>
</Container>
)}
</Box>
);
}
export default MenuMaintenance;

View File

@@ -1,101 +1,101 @@
import React, { useState } from 'react';
import './Model.css';
const Modal = ({ setNewUser, newUser, onSave }) => {
const [newUserState, setNewUserState] = useState(newUser);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleSave = () => {
const data = { ...newUserState };
onSave(data); // Pass the new data to the parent component
setIsModalOpen(false); // Close the modal after saving
};
const handleClose = () => {
setIsModalOpen(false);
};
const handleOpenModal = () => {
setIsModalOpen(true);
};
return (
<>
<button onClick={handleOpenModal}>ADD ITEM</button>
{isModalOpen && (
<div className='modalWrapper'>
<div className='modal'>
<button className="closeBtn" onClick={handleClose}>X</button>
<div className="input-group">
<label htmlFor="userId">User ID:</label>
<input
type="text"
id="userId"
value={newUserState.userId}
onChange={(e) => setNewUserState({ ...newUserState, userId: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
value={newUserState.username}
onChange={(e) => setNewUserState({ ...newUserState, username: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="fullName">Full Name:</label>
<input
type="text"
id="fullName"
value={newUserState.fullName}
onChange={(e) => setNewUserState({ ...newUserState, fullName: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="email">Email:</label>
<input
type="text"
id="email"
value={newUserState.email}
onChange={(e) => setNewUserState({ ...newUserState, email: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="mobileNumber">Mobile Number:</label>
<input
type="text"
id="mobileNumber"
value={newUserState.mobileNumber}
onChange={(e) => setNewUserState({ ...newUserState, mobileNumber: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="userGroup">User Group:</label>
<input
type="text"
id="userGroup"
value={newUserState.userGroup}
onChange={(e) => setNewUserState({ ...newUserState, userGroup: e.target.value })}
/>
</div>
<button onClick={handleSave}>Save</button>
</div>
</div>
)}
</>
);
};
export default Modal;
import React, { useState } from 'react';
import './Model.css';
const Modal = ({ setNewUser, newUser, onSave }) => {
const [newUserState, setNewUserState] = useState(newUser);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleSave = () => {
const data = { ...newUserState };
onSave(data); // Pass the new data to the parent component
setIsModalOpen(false); // Close the modal after saving
};
const handleClose = () => {
setIsModalOpen(false);
};
const handleOpenModal = () => {
setIsModalOpen(true);
};
return (
<>
<button onClick={handleOpenModal}>ADD ITEM</button>
{isModalOpen && (
<div className='modalWrapper'>
<div className='modal'>
<button className="closeBtn" onClick={handleClose}>X</button>
<div className="input-group">
<label htmlFor="userId">User ID:</label>
<input
type="text"
id="userId"
value={newUserState.userId}
onChange={(e) => setNewUserState({ ...newUserState, userId: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
value={newUserState.username}
onChange={(e) => setNewUserState({ ...newUserState, username: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="fullName">Full Name:</label>
<input
type="text"
id="fullName"
value={newUserState.fullName}
onChange={(e) => setNewUserState({ ...newUserState, fullName: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="email">Email:</label>
<input
type="text"
id="email"
value={newUserState.email}
onChange={(e) => setNewUserState({ ...newUserState, email: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="mobileNumber">Mobile Number:</label>
<input
type="text"
id="mobileNumber"
value={newUserState.mobileNumber}
onChange={(e) => setNewUserState({ ...newUserState, mobileNumber: e.target.value })}
/>
</div>
<div className="input-group">
<label htmlFor="userGroup">User Group:</label>
<input
type="text"
id="userGroup"
value={newUserState.userGroup}
onChange={(e) => setNewUserState({ ...newUserState, userGroup: e.target.value })}
/>
</div>
<button onClick={handleSave}>Save</button>
</div>
</div>
)}
</>
);
};
export default Modal;

View File

@@ -1,69 +1,69 @@
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.5);
}
.modal {
background: white;
padding: 20px;
border-radius: 8px;
width: 300px;
text-align: left;
position: relative;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.closeBtn {
position: absolute;
top: 10px;
right: 10px;
font-size: 16px;
cursor: pointer;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: white;
padding: 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.savedData {
margin-top: 20px;
}
.savedData h2 {
font-size: 16px;
margin-bottom: 10px;
}
.savedData p {
margin: 5px 0;
}
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.5);
}
.modal {
background: white;
padding: 20px;
border-radius: 8px;
width: 300px;
text-align: left;
position: relative;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.closeBtn {
position: absolute;
top: 10px;
right: 10px;
font-size: 16px;
cursor: pointer;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: white;
padding: 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.savedData {
margin-top: 20px;
}
.savedData h2 {
font-size: 16px;
margin-bottom: 10px;
}
.savedData p {
margin: 5px 0;
}

View File

@@ -1,29 +1,29 @@
.card-list {
display: flex;
flex-wrap: wrap; /* Allow cards to wrap to the next line */
justify-content: center; /* Horizontally center the cards */
}
.card {
border: 1px solid black;
padding: 10px;
margin: 10px; /* Adjust margin for better spacing */
width: calc(33.33% - 20px); /* Adjust card width based on container width */
max-width: 200px; /* Set maximum card width */
height: 150px; /* Adjust card height */
}
@media (max-width: 768px) {
.card {
width: calc(50% - 20px); /* Two cards per row on smaller screens */
}
}
@media (max-width: 480px) {
.card {
width: calc(100% - 20px); /* Single card per row on mobile devices */
}
}
.card-list {
display: flex;
flex-wrap: wrap; /* Allow cards to wrap to the next line */
justify-content: center; /* Horizontally center the cards */
}
.card {
border: 1px solid black;
padding: 10px;
margin: 10px; /* Adjust margin for better spacing */
width: calc(33.33% - 20px); /* Adjust card width based on container width */
max-width: 200px; /* Set maximum card width */
height: 150px; /* Adjust card height */
}
@media (max-width: 768px) {
.card {
width: calc(50% - 20px); /* Two cards per row on smaller screens */
}
}
@media (max-width: 480px) {
.card {
width: calc(100% - 20px); /* Single card per row on mobile devices */
}
}

View File

@@ -1,78 +1,21 @@
import React, { useState } from 'react';
import { FaUser, FaUsers, FaUtensils, FaLock, FaCogs, FaKey } from 'react-icons/fa';
import UserMaintance from './UserMaintance';
import UserGroupMaintance from './UserGroupMaintance/UserGroupMaintance';
import MenuMaintance from './MenuMaintance/MenuMaintance';
import MenuAccessControl from './MenuAccessControl/MenuAccessControl';
import SystemParameters from './SystemParameters/SystemParameters';
import ApiRegistery from './ApiRegistery/ApiRegistery';
import TokenRegistery from './TokenRegistery/TokenRegistery';
import Codeextension from './Codeextension.js';
import DynamicTable from './Dynamictable.js';
const Card = ({ title, content, icon: Icon, onClick }) => (
<div onClick={onClick} className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
<Icon className="text-4xl text-gray-800 mb-4" />
<h3 className="text-lg font-semibold">{title}</h3>
<p className="text-gray-600">{content}</p>
</div>
);
const CardList = () => {
const [showUserMaintance, setShowUserMaintance] = useState(false);
const [showUserGroupMaintance, setShowUserGroupMaintance] = useState(false);
const [showMenuMaintance, setShowMenuMaintance] = useState(false);
const [showMenuAccessControl, setShowMenuAccessControl] = useState(false);
const [showSystemParameters, setShowSystemParameters] = useState(false);
// const [showAccessType, setShowAccessType] = useState(false);
const [showApiRegistery, setShowApiRegistery] = useState(false);
const [showTokenRegistery, setShowTokenRegistery] = useState(false);
const [showCodeExtension, setShowCodeExtension] = useState(false);
const [showDynamicTable, setShowDynamicTable] = useState(false);
const handleCardClick = (menuItemDesc) => {
setShowUserMaintance(menuItemDesc === 'User Maintance');
setShowUserGroupMaintance(menuItemDesc === 'User Group Maintance');
setShowMenuMaintance(menuItemDesc === 'Menu Maintance');
setShowMenuAccessControl(menuItemDesc === 'Menu Access Control');
setShowSystemParameters(menuItemDesc === 'System Parameters');
// setShowAccessType(menuItemDesc === 'Access Type');
setShowApiRegistery(menuItemDesc === 'Api Registery');
setShowTokenRegistery(menuItemDesc === 'Token Registery');
setShowCodeExtension(menuItemDesc === 'Code Extension');
setShowDynamicTable(menuItemDesc === 'Dynamic Table');
};
return (
<>
{!showUserMaintance && !showUserGroupMaintance && !showMenuMaintance && !showMenuAccessControl && !showSystemParameters && !showApiRegistery && !showTokenRegistery && !showCodeExtension && !showDynamicTable && (
<div className="min-h-screen flex items-center justify-center bg-gray-100 p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<Card title="User Maintance" content="Manage users" icon={FaUser} onClick={() => handleCardClick('User Maintance')} />
<Card title="User Group Maintance" content="Manage user groups" icon={FaUsers} onClick={() => handleCardClick('User Group Maintance')} />
<Card title="Menu Maintance" content="Manage menus" icon={FaUtensils} onClick={() => handleCardClick('Menu Maintance')} />
<Card title="Menu Access Control" content="Control menu access" icon={FaLock} onClick={() => handleCardClick('Menu Access Control')} />
<Card title="System Parameters" content="Configure system parameters" icon={FaCogs} onClick={() => handleCardClick('System Parameters')} />
{/* <Card title="Access Type" content="Manage access types" icon={FaKey} onClick={() => handleCardClick('Access Type')} /> */}
<Card title="Api Registery" content="Manage APIs" icon={FaUser} onClick={() => handleCardClick('Api Registery')} />
<Card title="Token Registery" content="Manage tokens" icon={FaKey} onClick={() => handleCardClick('Token Registery')} />
<Card title="Code Extension" content="Extend code functionalities" icon={FaLock} onClick={() => handleCardClick('Code Extension')} />
<Card title="Dynamic Table" content="Dynamic data tables" icon={FaKey} onClick={() => handleCardClick('Dynamic Table')} />
</div>
</div>
)}
{showUserMaintance && <UserMaintance />}
{showUserGroupMaintance && <UserGroupMaintance />}
{showMenuMaintance && <MenuMaintance />}
{showMenuAccessControl && <MenuAccessControl />}
{showSystemParameters && <SystemParameters />}
{/* {showAccessType && <AccessType />} */}
{showApiRegistery && <ApiRegistery />}
{showTokenRegistery && <TokenRegistery />}
{showCodeExtension && <Codeextension />}
{showDynamicTable && <DynamicTable />}
</>
);
};
export default CardList;
import React from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import CardList from './CardList'; // Import the CardList component
const Setup = () => {
const navigate = useNavigate();
return (
<div style={{ padding: '20px' }}>
{/* Show CardList when at /dashboard/setup */}
{window.location.pathname === '/dashboard/setup' ? (
<CardList />
) : (
<Outlet />
)}
</div>
);
};
export default Setup;

View File

@@ -1,128 +1,219 @@
import React, { useState } from "react";
import { Button, Container, Grid, TextField } from "@mui/material";
const SystemParameterForm = () => {
const [formData, setFormData] = useState({
schedulerTimer: "",
leaseTaxCode: "",
vesselConfirmationProcessLimit: "",
rowToDisplay: "",
linkToDisplay: "",
rowToAdd: "",
lovRowToDisplay: "",
lovLinkToDisplay: "",
oldServerName: "",
oldBase: "",
oldAdminUser: "",
oldServerPort: "",
userDefaultGroup: "",
defaultDepartment: "",
defaultPosition: "",
singleCharge: "",
firstDayOfWeek: "",
hourPerShift: "",
cnBillingFrequency: "",
billingDepartmentCode: "",
basePriceList: "",
nonContainerServiceOrderAutoApprovalDeptCode: "",
ediMAESchedulerOnOff: "",
ediSchedulerOnOff: "",
companyDisplayName: "",
});
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData((prevState) => ({
...prevState,
[name]: value,
}));
};
const handleFileChange = (event) => {
setFormData((prevState) => ({
...prevState,
logo: event.target.files[0],
}));
};
const handleSubmit = (event) => {
event.preventDefault();
alert("Form submitted successfully!");
console.log("Form Data:", formData);
};
const handleClear = () => {
setFormData({
schedulerTimer: "",
leaseTaxCode: "",
vesselConfirmationProcessLimit: "",
rowToDisplay: "",
linkToDisplay: "",
rowToAdd: "",
lovRowToDisplay: "",
lovLinkToDisplay: "",
oldServerName: "",
oldBase: "",
oldAdminUser: "",
oldServerPort: "",
userDefaultGroup: "",
defaultDepartment: "",
defaultPosition: "",
singleCharge: "",
firstDayOfWeek: "",
hourPerShift: "",
cnBillingFrequency: "",
billingDepartmentCode: "",
basePriceList: "",
nonContainerServiceOrderAutoApprovalDeptCode: "",
ediMAESchedulerOnOff: "",
ediSchedulerOnOff: "",
companyDisplayName: "",
});
};
return (
<Container className="mt-5">
<p className="bg-gray-400 text-center text-white text-3xl m-3 p-2">
System Parameters
</p>
<form onSubmit={handleSubmit} className="m-5 p-5">
<Grid container spacing={3}>
{Object.keys(formData).map((key, index) => (
<Grid item xs={12} sm={6} key={index}>
<TextField
fullWidth
label={key
.split(/(?=[A-Z])/)
.join(" ")
.replace(/\b\w/g, (l) => l.toUpperCase())}
name={key}
value={formData[key]}
onChange={handleInputChange}
variant="outlined"
/>
</Grid>
))}
<Grid item xs={12}>
<input type="file" onChange={handleFileChange} />
</Grid>
</Grid>
<div style={{ textAlign: "end", marginTop: 20 }}>
<Button
variant="contained"
color="primary"
type="submit"
style={{ marginRight: 10 }}
>
Save
</Button>
<Button variant="outlined" color="primary" onClick={handleClear}>
Clear
</Button>
</div>
</form>
</Container>
);
};
export default SystemParameterForm;
import React, { useState, useEffect } from "react";
import {
Button,
Container,
Grid,
TextField,
Typography,
Paper,
Box,
LinearProgress
} from "@mui/material";
import { toast } from "react-toastify";
import { addSysParameter } from "../../../ApiServices/SytemparameterApi";
import { useSystemParameters } from "../../../context/SystemParameterContext";
const SystemParameterForm = () => {
const { systemParameters, loading: contextLoading } = useSystemParameters();
const [formData, setFormData] = useState({
schedulerTime: "",
leaseTaxCode: "",
vesselConfProcessLimit: "",
rowToDisplay: "",
linkToDisplay: "",
rowToAdd: "",
lovRowToDisplay: "",
lovLinkToDisplay: "",
oidserverName: "",
oidBase: "",
oidAdminUser: "",
oidServerPort: "",
userDefaultGroup: "",
defaultDepartment: "",
defaultPosition: "",
singleCharge: "",
firstDayOftheWeek: "",
hourPerShift: "",
cnBillingFrequency: "",
billingDepartmentCode: "",
basePriceList: "",
nonContainerServiceOrder: "",
ediMaeSchedulerONOFF: "",
ediSchedulerONOFF: "",
upload_Logo: null,
company_Display_Name: "",
});
useEffect(() => {
if (systemParameters) {
setFormData((prevForm) => ({
...prevForm,
...systemParameters,
}));
}
}, [systemParameters]);
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData((prevState) => ({
...prevState,
[name]: value,
}));
};
const handleFileChange = (event) => {
setFormData((prevState) => ({
...prevState,
upload_Logo: event.target.files[0],
}));
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
console.log("Form Data:", formData);
const sysParamData = await addSysParameter(formData);
console.log("API Response:", sysParamData);
toast.success("Form submitted successfully!");
} catch (error) {
console.error("Error:", error.response ? error.response.data : error.message);
toast.error("Failed to submit the form. Please try again.");
}
};
const handleClear = () => {
setFormData({
schedulerTime: "",
leaseTaxCode: "",
vesselConfProcessLimit: "",
rowToDisplay: "",
linkToDisplay: "",
rowToAdd: "",
lovRowToDisplay: "",
lovLinkToDisplay: "",
oidserverName: "",
oidBase: "",
oidAdminUser: "",
oidServerPort: "",
userDefaultGroup: "",
defaultDepartment: "",
defaultPosition: "",
singleCharge: "",
firstDayOftheWeek: "",
hourPerShift: "",
cnBillingFrequency: "",
billingDepartmentCode: "",
basePriceList: "",
nonContainerServiceOrder: "",
ediMaeSchedulerONOFF: "",
ediSchedulerONOFF: "",
upload_Logo: null,
company_Display_Name: "",
});
};
const formatLabel = (key) => {
return key
.split(/(?=[A-Z])/)
.join(" ")
.replace(/\b\w/g, (l) => l.toUpperCase());
};
return (
<Container maxWidth="lg" sx={{ mt: 10, mb: 4 }}>
{contextLoading ? (
<LinearProgress />
) : (
<Paper elevation={3} sx={{ p: 4 }}>
<Typography
variant="h4"
component="h1"
align="center"
sx={{
mb: 4,
color: 'primary.main',
fontWeight: 'bold'
}}
>
System Parameter Settings
</Typography>
<Box component="form" onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid item xs={6}>
<Typography variant="h6" color="text.secondary">
Setup Code
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="h6" color="text.secondary">
Value
</Typography>
</Grid>
{Object.keys(formData).map((key, index) =>
key !== "upload_Logo" ? (
<React.Fragment key={index}>
<Grid item xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
<Typography>{formatLabel(key)}</Typography>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
name={key}
value={formData[key]}
onChange={handleInputChange}
variant="outlined"
size="small"
/>
</Grid>
</React.Fragment>
) : (
<React.Fragment key={index}>
<Grid item xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
<Typography>Upload Logo</Typography>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
type="file"
onChange={handleFileChange}
variant="outlined"
size="small"
InputProps={{
inputProps: {
accept: "image/*"
}
}}
/>
</Grid>
</React.Fragment>
)
)}
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 4 }}>
<Button
variant="contained"
type="submit"
sx={{ mr: 2 }}
>
Save
</Button>
<Button
variant="outlined"
onClick={handleClear}
>
Clear
</Button>
</Box>
</Box>
</Paper>
)}
</Container>
);
};
export default SystemParameterForm;

View File

@@ -0,0 +1,427 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Snackbar,
Alert,
Typography,
TextField,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Paper,
IconButton,
InputAdornment
} from "@mui/material";
import { makeStyles } from '@mui/styles';
import SearchIcon from '@mui/icons-material/Search';
const API_URL = "http://34.198.218.30:30179/entityBuilder/Gaurav_testing";
const token = localStorage.getItem("authToken");
// Custom styles using makeStyles
const useStyles = makeStyles((theme) => ({
tableHeader: {
backgroundColor: "#000000",
color: "#ffffff",
},
searchContainer: {
display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(2),
},
searchInput: {
marginRight: theme.spacing(2),
flex: 1,
},
tableContainer: {
marginTop: theme.spacing(2),
},
dialogContent: {
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
},
formControl: {
marginBottom: theme.spacing(2),
},
button: {
margin: theme.spacing(1),
},
}));
const EntityTable = () => {
const classes = useStyles();
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [newEntity, setNewEntity] = useState({
name: "",
email: "",
mobno: "",
address: "",
pincode: "",
});
const [editEntity, setEditEntity] = useState(null);
const [showEditModal, setShowEditModal] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [deleteEntityId, setDeleteEntityId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [itemsPerPage] = useState(5); // Adjust this value as needed
const [searchQuery, setSearchQuery] = useState("");
const [openSnackbar, setOpenSnackbar] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState("");
const [snackbarSeverity, setSnackbarSeverity] = useState("success");
const handlePageChange = (event, newPage) => {
setCurrentPage(newPage);
};
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
handleSearch();
}, [searchQuery, data]);
const fetchData = async () => {
try {
const response = await axios.get(API_URL, {
headers: { Authorization: `Bearer ${token}` },
});
setData(response.data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
const handleDelete = async () => {
try {
await axios.delete(`${API_URL}/${deleteEntityId}`, {
headers: { Authorization: `Bearer ${token}` },
});
fetchData();
setSnackbarMessage("Successfully deleted!");
setSnackbarSeverity("success");
setOpenSnackbar(true);
setShowDeleteModal(false);
} catch (error) {
console.error("Error deleting data:", error);
setSnackbarMessage("Failed to delete!");
setSnackbarSeverity("error");
setOpenSnackbar(true);
}
};
const handleAdd = async () => {
try {
await axios.post(API_URL, newEntity, {
headers: { Authorization: `Bearer ${token}` },
});
fetchData();
setNewEntity({
name: "",
email: "",
mobno: "",
address: "",
pincode: "",
});
setShowAddModal(false);
setSnackbarMessage("Successfully added!");
setSnackbarSeverity("success");
setOpenSnackbar(true);
} catch (error) {
console.error("Error adding data:", error);
setSnackbarMessage("Failed to add!");
setSnackbarSeverity("error");
setOpenSnackbar(true);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setNewEntity({ ...newEntity, [name]: value });
};
const handleEditChange = (e) => {
const { name, value } = e.target;
setEditEntity({ ...editEntity, [name]: value });
};
const handleEdit = (entity) => {
setEditEntity(entity);
setShowEditModal(true);
};
const handleUpdate = async () => {
try {
await axios.put(`${API_URL}/${editEntity.id}`, editEntity, {
headers: { Authorization: `Bearer ${token}` },
});
fetchData();
setShowEditModal(false);
setSnackbarMessage("Successfully updated!");
setSnackbarSeverity("success");
setOpenSnackbar(true);
} catch (error) {
console.error("Error updating data:", error);
setSnackbarMessage("Failed to update!");
setSnackbarSeverity("error");
setOpenSnackbar(true);
}
};
const handleSearch = () => {
const filtered = data.filter(
(entity) =>
entity.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
entity.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
entity.mobno.toLowerCase().includes(searchQuery.toLowerCase()) ||
entity.address.toLowerCase().includes(searchQuery.toLowerCase()) ||
entity.pincode.toLowerCase().includes(searchQuery.toLowerCase())
);
setFilteredData(filtered);
};
return (
<div className="container mt-5">
<Typography variant="h4" gutterBottom>
Entity Table
</Typography>
<div className={classes.searchContainer}>
<Button
variant="contained"
color="primary"
onClick={() => setShowAddModal(true)}
className={classes.button}
>
Add Entity
</Button>
<TextField
className={classes.searchInput}
label="Search"
variant="outlined"
size="small"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton>
<SearchIcon />
</IconButton>
</InputAdornment>
),
}}
/>
</div>
<TableContainer component={Paper} className={classes.tableContainer}>
<Table>
<TableHead>
<TableRow className={classes.tableHeader}>
<TableCell>ID</TableCell>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Mobile No</TableCell>
<TableCell>Address</TableCell>
<TableCell>Pincode</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredData.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage).map((entity) => (
<TableRow key={entity.id}>
<TableCell>{entity.id}</TableCell>
<TableCell>{entity.name}</TableCell>
<TableCell>{entity.email}</TableCell>
<TableCell>{entity.mobno}</TableCell>
<TableCell>{entity.address}</TableCell>
<TableCell>{entity.pincode}</TableCell>
<TableCell>
<Button
variant="contained"
color="warning"
size="small"
className={classes.button}
onClick={() => handleEdit(entity)}
>
Update
</Button>
<Button
variant="contained"
color="error"
size="small"
className={classes.button}
onClick={() => {
setDeleteEntityId(entity.id);
setShowDeleteModal(true);
}}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={filteredData.length}
rowsPerPage={itemsPerPage}
page={currentPage}
onPageChange={handlePageChange}
/>
<Dialog open={showEditModal} onClose={() => setShowEditModal(false)}>
<DialogTitle>Edit Entity</DialogTitle>
<DialogContent className={classes.dialogContent}>
<TextField
label="Name"
variant="outlined"
fullWidth
name="name"
value={editEntity?.name || ""}
onChange={handleEditChange}
className={classes.formControl}
/>
<TextField
label="Email"
variant="outlined"
fullWidth
name="email"
value={editEntity?.email || ""}
onChange={handleEditChange}
className={classes.formControl}
/>
<TextField
label="Mobile No"
variant="outlined"
fullWidth
name="mobno"
value={editEntity?.mobno || ""}
onChange={handleEditChange}
className={classes.formControl}
/>
<TextField
label="Address"
variant="outlined"
fullWidth
name="address"
value={editEntity?.address || ""}
onChange={handleEditChange}
className={classes.formControl}
/>
<TextField
label="Pincode"
variant="outlined"
fullWidth
name="pincode"
value={editEntity?.pincode || ""}
onChange={handleEditChange}
className={classes.formControl}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowEditModal(false)} color="primary">
Cancel
</Button>
<Button onClick={handleUpdate} color="primary">
Update
</Button>
</DialogActions>
</Dialog>
<Dialog open={showAddModal} onClose={() => setShowAddModal(false)}>
<DialogTitle>Add Entity</DialogTitle>
<DialogContent className={classes.dialogContent}>
<TextField
label="Name"
variant="outlined"
fullWidth
name="name"
value={newEntity.name}
onChange={handleChange}
className={classes.formControl}
/>
<TextField
label="Email"
variant="outlined"
fullWidth
name="email"
value={newEntity.email}
onChange={handleChange}
className={classes.formControl}
/>
<TextField
label="Mobile No"
variant="outlined"
fullWidth
name="mobno"
value={newEntity.mobno}
onChange={handleChange}
className={classes.formControl}
/>
<TextField
label="Address"
variant="outlined"
fullWidth
name="address"
value={newEntity.address}
onChange={handleChange}
className={classes.formControl}
/>
<TextField
label="Pincode"
variant="outlined"
fullWidth
name="pincode"
value={newEntity.pincode}
onChange={handleChange}
className={classes.formControl}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowAddModal(false)} color="primary">
Cancel
</Button>
<Button onClick={handleAdd} color="primary">
Add
</Button>
</DialogActions>
</Dialog>
<Dialog open={showDeleteModal} onClose={() => setShowDeleteModal(false)}>
<DialogTitle>Confirm Delete</DialogTitle>
<DialogContent>
<Typography>Are you sure you want to delete this entity?</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowDeleteModal(false)} color="primary">
Cancel
</Button>
<Button onClick={handleDelete} color="primary">
Delete
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={openSnackbar}
autoHideDuration={6000}
onClose={() => setOpenSnackbar(false)}
>
<Alert onClose={() => setOpenSnackbar(false)} severity={snackbarSeverity}>
{snackbarMessage}
</Alert>
</Snackbar>
</div>
);
};
export default EntityTable;

View File

@@ -1,145 +1,746 @@
import React, { useState, useEffect, useRef } from "react";
import { Box, Button } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer className="flex flex-wrap justify-between p-2 bg-gray-100 border-b border-gray-200">
<Button
onClick={handleGoToPage1}
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 m-1"
>
Go to page 1
</Button>
<Button
onClick={handleModal}
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600 m-1"
>
+
</Button>
</GridToolbarContainer>
);
}
function TokenRegistry() {
const [menuItems, setMenuItems] = useState([]);
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
const [, setIsModalOpen] = useState(false);
const apiRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token"); // Get token from local storage
try {
const response = await fetch(
`${api}/apiregistery/getall`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
setMenuItems(data);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (menuItemId) => {
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
};
const columns = [
{
field: "table_id",
headerName: "Table ID",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "token_name",
headerName: "Token Name",
width: 250,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "token",
headerName: "Token",
width: 200,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div className="relative">
<div
className="cursor-pointer"
onClick={() => handleThreeDotsClick(row.id)}
>
<BsThreeDotsVertical /> {/* Updated icon from react-icons */}
</div>
{selectedMenuItem === row.id && (
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
Edit
</button>
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
Delete
</button>
</div>
)}
</div>
),
},
];
return (
<div className="flex justify-center mt-5 px-2 md:px-0">
<Box className="w-full max-w-7xl bg-gray-50 p-4 md:p-6 rounded shadow-md">
<p className="text-2xl md:text-3xl text-center text-white bg-gray-400 mb-4 p-3">
Token Registry
</p>
<div className="bg-white p-2 md:p-4 rounded shadow-md">
<DataGrid
rows={menuItems}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleModal={() => setIsModalOpen(true)}
/>
),
}}
pageSize={10}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
className="data-grid"
/>
</div>
</Box>
{/* Add your modals and other components here */}
</div>
);
}
export default TokenRegistry;
import React, { useState, useEffect } from "react";
import {
Button,
Modal,
FormControl,
InputLabel,
Select,
MenuItem,
TextField,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Pagination,
IconButton,
Checkbox,
FormControlLabel,
Chip,
Box,
Typography,
Card,
CardHeader,
CardContent,
Divider,
Grid,
TextareaAutosize,
Popover,
List,
ListItem,
ListItemText,
ListItemIcon,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContentText,
CircularProgress
} from "@mui/material";
import {
Edit as EditIcon,
Delete as DeleteIcon,
Add as AddIcon,
Menu as MenuIcon,
Close as CloseIcon,
Search as SearchIcon,
LibraryBooks as LibraryBooksIcon,
CheckCircle as CheckCircleIcon
} from "@mui/icons-material";
import { toast } from "react-toastify";
import * as tokenRegistryAPI from './tokenregistryapi';
function TOKENRegistry() {
const [tokens, setTokens] = useState([]);
const [showAddEditModal, setShowAddEditModal] = useState(false);
const [showGenerateTokenModal, setShowGenerateTokenModal] = useState(false);
const [newTokenName, setNewTokenName] = useState("");
const [generatedToken, setGeneratedToken] = useState("");
const [currentToken, setCurrentToken] = useState({
id: "",
tokenName: "",
tokenValue: "",
isActive: true,
scopes: []
});
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [visibleColumns, setVisibleColumns] = useState({
id: true,
tokenName: true,
tokenValue: true,
scopes: true,
isActive: true,
actions: true
});
const [loading, setLoading] = useState(true);
const [selectedScope, setSelectedScope] = useState("");
const [selectedScopes, setSelectedScopes] = useState([]);
const [availableScopes] = useState([
{ value: 'read', label: 'Read Access' },
{ value: 'write', label: 'Write Access' },
{ value: 'delete', label: 'Delete Access' },
{ value: 'admin', label: 'Admin Access' },
]);
const [anchorEl, setAnchorEl] = useState(null);
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [tokenToDelete, setTokenToDelete] = useState(null);
useEffect(() => {
fetchTokens();
}, []);
const fetchTokens = async () => {
setLoading(true);
try {
const data = await tokenRegistryAPI.fetchAllTokens();
setTokens(data);
} catch (error) {
handleApiError(error, "fetch tokens");
} finally {
setLoading(false);
}
};
const handleApiError = (error, action) => {
if (error.message === 'Unauthorized') {
toast.error("Your session has expired. Please log in again.");
} else {
toast.error(`Failed to ${action}`);
console.error(`Error ${action}:`, error);
}
};
const toggleColumn = (column) => {
setVisibleColumns(prev => ({
...prev,
[column]: !prev[column],
}));
};
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setCurrentToken(prev => ({
...prev,
[name]: type === "checkbox" ? checked : value
}));
};
const handleSearch = (query) => {
setSearchQuery(query);
setCurrentPage(1);
};
const handleClose = () => {
setShowAddEditModal(false);
setShowGenerateTokenModal(false);
setGeneratedToken("");
setNewTokenName("");
setSelectedScopes([]);
};
const handleRecordsPerPageChange = (number) => {
setRecordsPerPage(number);
setCurrentPage(1);
setAnchorEl(null);
};
const filteredTokens = tokens.filter(
(item) =>
item.tokenName &&
item.tokenName.toLowerCase().includes(searchQuery.toLowerCase())
);
const totalPages = Math.ceil(filteredTokens.length / recordsPerPage);
const handlePageChange = (event, pageNumber) => {
setCurrentPage(pageNumber);
};
const slicedTokens = filteredTokens.slice(
(currentPage - 1) * recordsPerPage,
currentPage * recordsPerPage
);
const handleSubmit = async (event) => {
event.preventDefault();
try {
if (isEditing) {
await tokenRegistryAPI.updateToken(currentToken.id, currentToken);
toast.success("Token updated successfully!");
} else {
await tokenRegistryAPI.createToken(currentToken);
toast.success("Token added successfully!");
}
setShowAddEditModal(false);
fetchTokens();
} catch (error) {
handleApiError(error, isEditing ? "update token" : "add token");
}
};
const openModal = (token = { id: "", tokenName: "", tokenValue: "", isActive: false, scopes: [] }) => {
setIsEditing(!!token.id);
setCurrentToken(token);
setSelectedScopes(token.scopes || []);
setShowAddEditModal(true);
};
const handleDeleteClick = (id) => {
setTokenToDelete(id);
setDeleteConfirmOpen(true);
};
const handleDeleteConfirm = async () => {
try {
await tokenRegistryAPI.deleteToken(tokenToDelete);
toast.success('Token deleted successfully');
fetchTokens();
} catch (error) {
handleApiError(error, "delete token");
} finally {
setDeleteConfirmOpen(false);
setTokenToDelete(null);
}
};
const addScope = () => {
if (selectedScope && !selectedScopes.includes(selectedScope)) {
setSelectedScopes([...selectedScopes, selectedScope]);
setSelectedScope("");
}
};
const removeScope = (scopeToRemove) => {
setSelectedScopes(selectedScopes.filter(scope => scope !== scopeToRemove));
};
const generateNewToken = async () => {
if (!newTokenName.trim()) {
toast.error("Please enter a token name");
return;
}
try {
const data = await tokenRegistryAPI.generateToken({
name: newTokenName,
scopes: selectedScopes
});
setGeneratedToken(data.tokenValue);
toast.success("Token generated successfully!");
fetchTokens();
} catch (error) {
handleApiError(error, "generate token");
}
};
const handleMenuClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleColumnsMenuClick = (event) => {
setColumnsAnchorEl(event.currentTarget);
};
const handleColumnsMenuClose = () => {
setColumnsAnchorEl(null);
};
return (
<Box sx={{ marginTop: "1rem", padding: 2 }}>
{loading ? (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
<CircularProgress />
</Box>
) : (
<Box>
{/* Token Generation Card */}
<Card sx={{ mb: 4, boxShadow: 3 }}>
<CardHeader
title="Token Management"
action={
<Button
variant="contained"
onClick={() => setShowGenerateTokenModal(true)}
sx={{ backgroundColor: '#1976d2', color: 'white' }}
>
Generate New Token
</Button>
}
/>
<CardContent>
<Typography variant="body1">
Manage your API tokens here. Generate new tokens or delete existing ones.
</Typography>
</CardContent>
</Card>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
Token Registry
</Typography>
</Box>
<Grid container spacing={2} alignItems="center" mb={3}>
<Grid item xs={12} md={8} lg={6}>
<TextField
fullWidth
variant="outlined"
placeholder="Search"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
InputProps={{
startAdornment: (
<SearchIcon sx={{ color: 'action.active', mr: 1 }} />
),
sx: {
borderRadius: '10px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
}
}}
/>
</Grid>
<Grid item xs={12} md={4} lg={6} display="flex" justifyContent="flex-end">
<IconButton
onClick={() => openModal()}
color="primary"
sx={{ mr: 2 }}
>
<AddIcon />
</IconButton>
<IconButton
onClick={handleColumnsMenuClick}
color="primary"
>
<MenuIcon />
</IconButton>
</Grid>
</Grid>
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
<Table>
<TableHead sx={{ backgroundColor: '#f5f5f5' }}>
<TableRow>
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
<TableCell key={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{slicedTokens.length === 0 ? (
<TableRow>
<TableCell
colSpan={Object.keys(visibleColumns).filter(key => visibleColumns[key]).length}
align="center"
>
No Data Available
</TableCell>
</TableRow>
) : (
slicedTokens.map((token, index) => (
<TableRow key={index} hover>
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
<TableCell key={key}>
{key === "actions" ? (
<>
<IconButton
onClick={() => openModal(token)}
color="primary"
sx={{ mr: 1 }}
>
<EditIcon />
</IconButton>
<IconButton
onClick={() => handleDeleteClick(token.id)}
color="error"
>
<DeleteIcon />
</IconButton>
</>
) : key === "isActive" ? (
<Chip
label={token.isActive ? "Active" : "Inactive"}
color={token.isActive ? "success" : "error"}
size="small"
sx={{
backgroundColor: token.isActive ? '#d4f7d4' : '#f7d4d4',
color: token.isActive ? 'green' : 'red'
}}
/>
) : key === "scopes" ? (
<Box>
{token.scopes && token.scopes.map(scope => {
const scopeInfo = availableScopes.find(s => s.value === scope);
return (
<Chip
key={scope}
label={scopeInfo?.label || scope}
color="primary"
size="small"
sx={{ mr: 1, mb: 1 }}
/>
);
})}
</Box>
) : (
token[key]
)}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Manage Columns */}
<Popover
open={Boolean(columnsAnchorEl)}
anchorEl={columnsAnchorEl}
onClose={handleColumnsMenuClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<List>
{Object.keys(visibleColumns).map((column) => (
<ListItem key={column} dense button onClick={() => toggleColumn(column)}>
<ListItemIcon>
<Checkbox
edge="start"
checked={visibleColumns[column]}
tabIndex={-1}
disableRipple
/>
</ListItemIcon>
<ListItemText primary={column.charAt(0).toUpperCase() + column.slice(1).toLowerCase()} />
</ListItem>
))}
</List>
</Popover>
{/* Records Per Page */}
<Box display="flex" justifyContent="flex-end" mt={2}>
<Button
variant="outlined"
startIcon={<LibraryBooksIcon />}
onClick={handleMenuClick}
sx={{ border: '2px solid', borderRadius: '8px', boxShadow: 1 }}
>
{recordsPerPage}
</Button>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={handleMenuClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<List>
{[1, 5, 10, 20, 50].map((number) => (
<ListItem
key={number}
dense
button
onClick={() => handleRecordsPerPageChange(number)}
sx={{ minWidth: '100px' }}
>
<ListItemText primary={number} />
{recordsPerPage === number && <CheckCircleIcon color="primary" />}
</ListItem>
))}
</List>
</Popover>
</Box>
<Box display="flex" justifyContent="center" mt={2}>
<Pagination
count={totalPages}
page={currentPage}
onChange={handlePageChange}
color="primary"
showFirstButton
showLastButton
/>
</Box>
{/* Generate Token Modal */}
<Dialog open={showGenerateTokenModal} onClose={handleClose} fullWidth maxWidth="sm">
<DialogTitle>
Generate New Token
<IconButton
onClick={handleClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box component="form" sx={{ mt: 1 }}>
<TextField
margin="normal"
fullWidth
label="Token Name"
value={newTokenName}
onChange={(e) => setNewTokenName(e.target.value)}
required
sx={{ mb: 2 }}
/>
<Typography variant="subtitle1" gutterBottom>
Token Scopes
</Typography>
<Box display="flex" alignItems="center" mb={2}>
<FormControl fullWidth sx={{ mr: 2 }}>
<InputLabel>Select a scope</InputLabel>
<Select
value={selectedScope}
onChange={(e) => setSelectedScope(e.target.value)}
label="Select a scope"
>
<MenuItem value=""><em>Select a scope</em></MenuItem>
{availableScopes.map(scope => (
<MenuItem key={scope.value} value={scope.value}>
{scope.label}
</MenuItem>
))}
</Select>
</FormControl>
<Button
variant="outlined"
onClick={addScope}
disabled={!selectedScope}
>
Add Scope
</Button>
</Box>
<Typography variant="caption" display="block" gutterBottom>
Select and add the permissions this token should have
</Typography>
{/* Display selected scopes as chips */}
{selectedScopes.length > 0 && (
<Box sx={{ mt: 2 }}>
{selectedScopes.map(scope => {
const scopeInfo = availableScopes.find(s => s.value === scope);
return (
<Chip
key={scope}
label={scopeInfo?.label || scope}
color="primary"
onDelete={() => removeScope(scope)}
sx={{ mr: 1, mb: 1 }}
/>
);
})}
</Box>
)}
{generatedToken && (
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle1" gutterBottom>
Generated Token
</Typography>
<TextareaAutosize
minRows={3}
value={generatedToken}
readOnly
style={{ width: '100%', padding: '8px', borderRadius: '4px', borderColor: '#ccc' }}
/>
<Typography variant="caption" display="block" gutterBottom>
Copy this token and store it securely. You won't be able to see it again.
</Typography>
</Box>
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
<Button
variant="contained"
onClick={generateNewToken}
disabled={!newTokenName.trim()}
>
Generate Token
</Button>
</DialogActions>
</Dialog>
{/* Add/Edit Token Modal */}
<Dialog open={showAddEditModal} onClose={handleClose} fullWidth maxWidth="sm">
<DialogTitle>
{isEditing ? "Edit Token" : "Add Token"}
<IconButton
onClick={handleClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
margin="normal"
fullWidth
label="Token Name"
name="tokenName"
value={currentToken.tokenName}
onChange={handleInputChange}
required
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Token Value"
name="tokenValue"
value={currentToken.tokenValue}
onChange={handleInputChange}
required
sx={{ mb: 2 }}
/>
<Typography variant="subtitle1" gutterBottom>
Token Scopes
</Typography>
<Box display="flex" alignItems="center" mb={2}>
<FormControl fullWidth sx={{ mr: 2 }}>
<InputLabel>Select a scope</InputLabel>
<Select
value={selectedScope}
onChange={(e) => setSelectedScope(e.target.value)}
label="Select a scope"
>
<MenuItem value=""><em>Select a scope</em></MenuItem>
{availableScopes.map(scope => (
<MenuItem key={scope.value} value={scope.value}>
{scope.label}
</MenuItem>
))}
</Select>
</FormControl>
<Button
variant="outlined"
onClick={() => {
if (selectedScope && !currentToken.scopes.includes(selectedScope)) {
setCurrentToken(prev => ({
...prev,
scopes: [...prev.scopes, selectedScope]
}));
setSelectedScope("");
}
}}
disabled={!selectedScope}
>
Add Scope
</Button>
</Box>
<Typography variant="caption" display="block" gutterBottom>
Select and add the permissions this token should have
</Typography>
{/* Display selected scopes as chips */}
{currentToken.scopes && currentToken.scopes.length > 0 && (
<Box sx={{ mt: 2 }}>
{currentToken.scopes.map(scope => {
const scopeInfo = availableScopes.find(s => s.value === scope);
return (
<Chip
key={scope}
label={scopeInfo?.label || scope}
color="primary"
onDelete={() => {
setCurrentToken(prev => ({
...prev,
scopes: prev.scopes.filter(s => s !== scope)
}));
}}
sx={{ mr: 1, mb: 1 }}
/>
);
})}
</Box>
)}
<FormControlLabel
control={
<Checkbox
checked={currentToken.isActive}
onChange={handleInputChange}
name="isActive"
color="primary"
/>
}
label="Active?"
sx={{ mt: 2 }}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
<Button
variant="contained"
onClick={handleSubmit}
type="submit"
>
{isEditing ? "Update Token" : "Add Token"}
</Button>
</DialogActions>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog
open={deleteConfirmOpen}
onClose={() => setDeleteConfirmOpen(false)}
>
<DialogTitle>Confirm Delete</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to delete this token?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteConfirmOpen(false)}>Cancel</Button>
<Button onClick={handleDeleteConfirm} color="error" autoFocus>
Delete
</Button>
</DialogActions>
</Dialog>
</Box>
)}
</Box>
);
}
export default TOKENRegistry;

View File

@@ -0,0 +1,88 @@
// src/services/tokenRegistryAPI.js
import axios from 'axios';
import { getToken } from '../../../utils/tokenService';
// Create axios instance with base configuration
const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://157.66.191.31:33730/back/',
});
// Add request interceptor to inject token
apiClient.interceptors.request.use((config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
// Response interceptor for error handling
apiClient.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response) {
// Handle specific status codes
if (error.response.status === 401) {
// Handle unauthorized (token expired)
console.error('Authentication failed');
}
throw new Error(error.response.data.message || 'Request failed');
}
throw new Error(error.message || 'Network error');
}
);
export const fetchAllTokens = async () => {
try {
return await apiClient.get('apiregistery/getall');
} catch (error) {
console.error('Fetch tokens error:', error);
throw error;
}
};
export const generateToken = async (token_name) => {
try {
return await apiClient.post(
'apiregistery/generateToken',
new URLSearchParams({ token_name }),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
} catch (error) {
console.error('Generate token error:', error);
throw error;
}
};
export const createToken = async (tokenData) => {
try {
return await apiClient.post('apiregistery/create', tokenData);
} catch (error) {
console.error('Create token error:', error);
throw error;
}
};
export const updateToken = async (id, tokenData) => {
try {
return await apiClient.put(`apiregistery/update/${id}`, tokenData);
} catch (error) {
console.error('Update token error:', error);
throw error;
}
};
export const deleteToken = async (id) => {
try {
return await apiClient.delete(`apiregistery/delete/${id}`);
} catch (error) {
console.error('Delete token error:', error);
throw error;
}
};

View File

@@ -1,41 +1,41 @@
/* UpdateModal.css */
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* UpdateModal.css */
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}

View File

@@ -1,107 +1,139 @@
import React, { useState, useEffect } from 'react';
const UpdateModal = ({ user, onUpdate, onClose }) => {
const [updatedUser, setUpdatedUser] = useState(() => ({ ...user }));
useEffect(() => {
console.log('Updated user state:', updatedUser);
}, [updatedUser]);
const handleChange = (field, value) => {
const updatedData = { ...updatedUser, [field]: value };
setUpdatedUser(updatedData);
};
const handleSave = () => {
onUpdate(updatedUser);
onClose();
};
// const handleUpdate = () => {
// console.log('Before update:', updatedUser);
// onUpdate(updatedUser);
// onClose();
// // Use a callback function with setUpdatedUser to ensure the state is updated
// setUpdatedUser((prev) => {
// const updatedData = { ...prev };
// console.log('Updated data:', updatedData);
// onUpdate(updatedData); // Pass the updatedData to onUpdate
// onClose();
// return updatedData; // Return the updatedData to setUpdatedUser
// });
// // Set the updatedUser state directly to ensure the UI reflects the changes
// setUpdatedUser(updatedUser);
// };
return (
<div className="modalWrapper">
<div className="modal">
<button className="closeBtn" onClick={onClose}>
X
</button>
<div>User ID: {user.userId}</div>
<div>Username: {user.username}</div>
<div>Full Name: {user.fullName}</div>
<div>Email: {user.email}</div>
<div>Mob Number: {user.mobno}</div>
<div>User grp name: {user.usergrpname}</div>
<label htmlFor="updatedUserId">User ID:</label>
<input
id="updatedUserId"
type="text"
value={updatedUser.userId}
onChange={(e) => handleChange('userId', e.target.value)}
/>
<label htmlFor="updatedUsername">Username:</label>
<input
id="updatedUsername"
type="text"
value={updatedUser.username}
onChange={(e) => handleChange('username', e.target.value)}
/>
<label htmlFor="updatedEmail">Email:</label>
<input
id="updatedEmail"
type="text"
value={updatedUser.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
<label htmlFor="updatedFullName">Full Name:</label>
<input
id="updatedFullName"
type="text"
value={updatedUser.fullName}
onChange={(e) => handleChange('fullName', e.target.value)}
/>
<label htmlFor="updatedMobno">Mob No:</label>
<input
id="updatedmobno"
type="number"
value={updatedUser.mobno}
onChange={(e) => handleChange('mobno', e.target.value)}
/>
<label htmlFor="updatedusergrpname">User grp name:</label>
<input
id="updatedusergrpname"
type="text"
value={updatedUser.usergrpname}
onChange={(e) => handleChange('usergrpname', e.target.value)}
/>
<button onClick={handleSave}>SAVE</button>
</div>
</div>
);
};
export default UpdateModal;
import React, { useState, useEffect } from 'react';
import {
TextField,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
CircularProgress
} from '@mui/material';
const UpdateModal = ({ user, onUpdate, onClose, open }) => {
const [updatedUser, setUpdatedUser] = useState({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Initialize form with user data when modal opens or user changes
useEffect(() => {
if (user) {
setUpdatedUser({ ...user });
}
}, [user, open]);
const handleChange = (field, value) => {
setUpdatedUser(prev => ({
...prev,
[field]: value
}));
};
const handleSave = async () => {
if (!updatedUser.userId) {
setError('User ID is required');
return;
}
setLoading(true);
setError(null);
try {
await onUpdate(updatedUser);
onClose();
} catch (err) {
console.error('Update failed:', err);
setError(err.message || 'Failed to update user');
} finally {
setLoading(false);
}
};
if (!user) {
return null; // Or render a loading state/message
}
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>Update User</DialogTitle>
<DialogContent>
{error && (
<div style={{ color: 'red', marginBottom: '16px' }}>
{error}
</div>
)}
<TextField
margin="dense"
label="User ID"
fullWidth
variant="outlined"
value={updatedUser.userId || ''}
onChange={(e) => handleChange('userId', e.target.value)}
disabled // Typically IDs shouldn't be editable
/>
<TextField
margin="dense"
label="Username"
fullWidth
variant="outlined"
value={updatedUser.username || ''}
onChange={(e) => handleChange('username', e.target.value)}
required
/>
<TextField
margin="dense"
label="Full Name"
fullWidth
variant="outlined"
value={updatedUser.fullName || ''}
onChange={(e) => handleChange('fullName', e.target.value)}
/>
<TextField
margin="dense"
label="Email"
fullWidth
variant="outlined"
type="email"
value={updatedUser.email || ''}
onChange={(e) => handleChange('email', e.target.value)}
/>
<TextField
margin="dense"
label="Mobile Number"
fullWidth
variant="outlined"
value={updatedUser.mobno || ''}
onChange={(e) => handleChange('mobno', e.target.value)}
/>
<TextField
margin="dense"
label="User Group"
fullWidth
variant="outlined"
value={updatedUser.usergrpname || ''}
onChange={(e) => handleChange('usergrpname', e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="secondary">
Cancel
</Button>
<Button
onClick={handleSave}
color="primary"
variant="contained"
disabled={loading}
>
{loading ? <CircularProgress size={24} /> : 'Save'}
</Button>
</DialogActions>
</Dialog>
);
};
export default UpdateModal;

View File

@@ -1,69 +1,69 @@
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
max-width: 100%;
}
.closeBtn {
background: none;
border: none;
font-size: 18px;
color: #333;
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
button:last-child {
background-color: #36bbf4;
}
button:last-child:hover {
background-color: #2f8cd3;
}
.modalWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
max-width: 100%;
}
.closeBtn {
background: none;
border: none;
font-size: 18px;
color: #333;
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
button:last-child {
background-color: #36bbf4;
}
button:last-child:hover {
background-color: #2f8cd3;
}

View File

@@ -1,146 +1,146 @@
// AddUserGroupModal.js
import React, { useState } from 'react';
import './Modelitem.css';
const AddUserGroupModal = ({ showModal, handleCloseModal, onSave }) => {
const [newUserData, setNewUserData] = useState({
groupName: '',
groupDesc: '',
createBy: '',
createDate: '',
updateDate: '',
updateBy: '',
status: '',
groupLevel: '',
createDateFormatted: '',
updateDateFormatted: '',
// Add more fields as needed
});
const handleSave = () => {
// Format date fields before saving
const formattedData = {
...newUserData,
createDate: new Date(newUserData.createDate).toISOString(),
updateDate: new Date(newUserData.updateDate).toISOString(),
};
onSave(formattedData); // Pass the new data to the parent component
handleCloseModal(); // Close the modal after saving
};
const handleCancel = () => {
setNewUserData({
groupName: '',
groupDesc: '',
createBy: '',
createDate: '',
updateDate: '',
updateBy: '',
status: '',
groupLevel: '',
createDateFormatted: '',
updateDateFormatted: '',
// Reset other fields as needed
});
handleCloseModal();
};
return (
<>
{showModal && (
<div className='modalWrapper'>
<div className='modal'>
<button className="closeBtn" onClick={handleCancel}>X</button>
<label htmlFor="groupName">Group Name:</label>
<input
type="text"
id="groupName"
value={newUserData.groupName}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupName: e.target.value }))}
/>
<label htmlFor="groupDesc">Group Description:</label>
<input
type="text"
id="groupDesc"
value={newUserData.groupDesc}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupDesc: e.target.value }))}
/>
<label htmlFor="createBy">Create By:</label>
<input
type="text"
id="createBy"
value={newUserData.createBy}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createBy: e.target.value }))}
/>
<label htmlFor="createDate">Create Date:</label>
<input
type="date"
id="createDate"
value={newUserData.createDate}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createDate: e.target.value }))}
/>
<label htmlFor="updateDate">Update Date:</label>
<input
type="text"
id="updateDate"
value={newUserData.updateDate}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateDate: e.target.value }))}
/>
<label htmlFor="updateBy">Update By:</label>
<input
type="text"
id="updateBy"
value={newUserData.updateBy}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateBy: e.target.value }))}
/>
<label htmlFor="status">Status:</label>
<input
type="text"
id="status"
value={newUserData.status}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, status: e.target.value }))}
/>
<label htmlFor="groupLevel">Group Level:</label>
<input
type="text"
id="groupLevel"
value={newUserData.groupLevel}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupLevel: e.target.value }))}
/>
<label htmlFor="createDateFormatted">Create Date Formatted:</label>
<input
type="text"
id="createDateFormatted"
value={newUserData.createDateFormatted}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createDateFormatted: e.target.value }))}
/>
<label htmlFor="updateDateFormatted">Update Date Formatted:</label>
<input
type="text"
id="updateDateFormatted"
value={newUserData.updateDateFormatted}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateDateFormatted: e.target.value }))}
/>
<button onClick={handleSave}>Save</button>
<button onClick={handleCancel}>Cancel</button>
</div>
</div>
)}
</>
);
};
export default AddUserGroupModal;
// AddUserGroupModal.js
import React, { useState } from 'react';
import './Modelitem.css';
const AddUserGroupModal = ({ showModal, handleCloseModal, onSave }) => {
const [newUserData, setNewUserData] = useState({
groupName: '',
groupDesc: '',
createBy: '',
createDate: '',
updateDate: '',
updateBy: '',
status: '',
groupLevel: '',
createDateFormatted: '',
updateDateFormatted: '',
// Add more fields as needed
});
const handleSave = () => {
// Format date fields before saving
const formattedData = {
...newUserData,
createDate: new Date(newUserData.createDate).toISOString(),
updateDate: new Date(newUserData.updateDate).toISOString(),
};
onSave(formattedData); // Pass the new data to the parent component
handleCloseModal(); // Close the modal after saving
};
const handleCancel = () => {
setNewUserData({
groupName: '',
groupDesc: '',
createBy: '',
createDate: '',
updateDate: '',
updateBy: '',
status: '',
groupLevel: '',
createDateFormatted: '',
updateDateFormatted: '',
// Reset other fields as needed
});
handleCloseModal();
};
return (
<>
{showModal && (
<div className='modalWrapper'>
<div className='modal'>
<button className="closeBtn" onClick={handleCancel}>X</button>
<label htmlFor="groupName">Group Name:</label>
<input
type="text"
id="groupName"
value={newUserData.groupName}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupName: e.target.value }))}
/>
<label htmlFor="groupDesc">Group Description:</label>
<input
type="text"
id="groupDesc"
value={newUserData.groupDesc}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupDesc: e.target.value }))}
/>
<label htmlFor="createBy">Create By:</label>
<input
type="text"
id="createBy"
value={newUserData.createBy}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createBy: e.target.value }))}
/>
<label htmlFor="createDate">Create Date:</label>
<input
type="date"
id="createDate"
value={newUserData.createDate}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createDate: e.target.value }))}
/>
<label htmlFor="updateDate">Update Date:</label>
<input
type="text"
id="updateDate"
value={newUserData.updateDate}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateDate: e.target.value }))}
/>
<label htmlFor="updateBy">Update By:</label>
<input
type="text"
id="updateBy"
value={newUserData.updateBy}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateBy: e.target.value }))}
/>
<label htmlFor="status">Status:</label>
<input
type="text"
id="status"
value={newUserData.status}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, status: e.target.value }))}
/>
<label htmlFor="groupLevel">Group Level:</label>
<input
type="text"
id="groupLevel"
value={newUserData.groupLevel}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, groupLevel: e.target.value }))}
/>
<label htmlFor="createDateFormatted">Create Date Formatted:</label>
<input
type="text"
id="createDateFormatted"
value={newUserData.createDateFormatted}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, createDateFormatted: e.target.value }))}
/>
<label htmlFor="updateDateFormatted">Update Date Formatted:</label>
<input
type="text"
id="updateDateFormatted"
value={newUserData.updateDateFormatted}
onChange={(e) => setNewUserData((prevData) => ({ ...prevData, updateDateFormatted: e.target.value }))}
/>
<button onClick={handleSave}>Save</button>
<button onClick={handleCancel}>Cancel</button>
</div>
</div>
)}
</>
);
};
export default AddUserGroupModal;

View File

@@ -1,7 +1,133 @@
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}
/* UserGroupMaintenance.css */
.user-group-maintenance-container {
padding: 2rem;
background-color: #f5f5f5;
min-height: 100vh;
}
.main-title {
font-family: 'PT Serif', serif;
color: #333;
text-align: center;
margin-bottom: 2rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
}
.data-grid-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 1rem;
}
.grid-toolbar {
padding: 0.5rem 0;
margin-bottom: 1rem;
display: flex;
gap: 1rem;
}
.toolbar-button {
font-family: 'PT Serif', serif;
text-transform: capitalize;
font-weight: 600;
background-color: #1976d2;
color: white;
transition: all 0.3s ease;
}
.toolbar-button:hover {
background-color: #1565c0;
}
.add-button {
background-color: #4caf50;
}
.add-button:hover {
background-color: #388e3c;
}
.custom-header {
font-family: 'PT Serif', serif;
font-weight: 700;
background-color: #f0f0f0;
color: #333;
}
.custom-cell {
font-family: 'PT Serif', serif;
font-weight: 400;
}
.three-dots {
cursor: pointer;
padding: 0.5rem;
color: #666;
}
.popover {
position: absolute;
right: 0;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 100;
padding: 0.5rem;
border-radius: 4px;
}
.popover button {
display: block;
width: 100%;
padding: 0.5rem 1rem;
text-align: left;
background: none;
border: none;
cursor: pointer;
font-family: 'PT Serif', serif;
}
.popover button:hover {
background-color: #f5f5f5;
}
/* Modal styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
padding: 2rem;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal-title {
font-family: 'PT Serif', serif;
font-size: 1.5rem;
margin-bottom: 1.5rem;
color: #333;
}

View File

@@ -1,221 +1,223 @@
import React, { useState, useEffect, useRef } from "react";
import { Box, Button } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { BsThreeDotsVertical } from "react-icons/bs";
import "./UserGroupMaintance.css";
// eslint-disable-next-line
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer>
<Button onClick={handleGoToPage1}>Go to page 1</Button>
<Button onClick={handleModal}>+</Button>
</GridToolbarContainer>
);
}
function UserMaintance() {
const [userGroups, setUserGroups] = useState([]);
const [selectedUserGroup, setSelectedUserGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const apiRef = useRef(null);
// eslint-disable-next-line
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token");
try {
const response = await fetch(`${api}/api/getAllUsrGrp`, {
headers: { authorization: `bearer ${token}` },
});
const data = await response.json();
const userGroupsWithIds = data.map((group, index) => ({
...group,
id: index + 1,
}));
setUserGroups(userGroupsWithIds);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (usrGrp) => {
setSelectedUserGroup(usrGrp === selectedUserGroup ? null : usrGrp);
};
const handleDelete = async (usrGrp) => {
try {
const response = await fetch(`${api}/api/delete_usrgrp/${usrGrp}`, {
method: "DELETE",
});
if (response.ok) {
setUserGroups(userGroups.filter((group) => group.usrGrp !== usrGrp));
console.log("User group deleted successfully:", usrGrp);
} else {
console.error("Failed to delete user group:", response.statusText);
}
} catch (error) {
console.error("Error deleting user group:", error);
}
};
const handleModal = () => {
setIsModalOpen(true);
};
const columns = [
{
field: "usrGrp",
headerName: "User Group ID",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "groupName",
headerName: "Group Name",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "groupDesc",
headerName: "Group Description",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "createby",
headerName: "Create By",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "createdate",
headerName: "Create Date",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "updatedate",
headerName: "Update Date",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "updateby",
headerName: "Update By",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "status",
headerName: "Status",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "grouplevel",
headerName: "Group Level",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "createdateformated",
headerName: "Create Date Formated",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "updatedateformated",
headerName: "Update Date Formated",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
// Add other columns as needed
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div>
<div
className="three-dots"
onClick={() => handleThreeDotsClick(row.usrGrp)}
>
<BsThreeDotsVertical />
</div>
{selectedUserGroup === row.usrGrp && (
<div className="popover">
<button onClick={() => handleDelete(row.usrGrp)}>Delete</button>
{/* You can include other actions here */}
</div>
)}
</div>
),
},
];
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4">
<div className="text-center text-3xl text-white bg-gray-400 p-2 rounded-lg">
User Group Maintenance
</div>
<Box
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2 bg-white border border-gray-200 shadow-lg rounded-lg"
sx={{ height: 500, width: "100%" }}
>
<DataGrid
rows={userGroups}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleThreeDotsClick={handleThreeDotsClick}
handleModal={handleModal}
/>
),
}}
pageSize={10}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
className="bg-gray-400"
/>
</Box>
{/* Your modals and other components */}
{isModalOpen && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 p-4">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-lg w-full">
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
{/* Modal content here */}
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
</div>
</div>
)}
</div>
);
}
export default UserMaintance;
import React, { useState, useEffect, useRef } from "react";
import { Box, Button, Typography } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
import "./UserGroupMaintance.css";
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer className="grid-toolbar">
<Button
variant="contained"
className="toolbar-button"
onClick={handleGoToPage1}
>
Go to page 1
</Button>
<Button
variant="contained"
className="toolbar-button add-button"
onClick={handleModal}
>
Add User Group
</Button>
</GridToolbarContainer>
);
}
function UserMaintance() {
const [userGroups, setUserGroups] = useState([]);
const [selectedUserGroup, setSelectedUserGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const apiRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token");
try {
const response = await fetch(`${api}/api/getAllUsrGrp`, {
headers: { authorization: `bearer ${token}` },
});
const data = await response.json();
const userGroupsWithIds = data.map((group, index) => ({
...group,
id: index + 1,
}));
setUserGroups(userGroupsWithIds);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (usrGrp) => {
setSelectedUserGroup(usrGrp === selectedUserGroup ? null : usrGrp);
};
const handleDelete = async (usrGrp) => {
try {
const response = await fetch(`${api}/api/delete_usrgrp/${usrGrp}`, {
method: "DELETE",
});
if (response.ok) {
setUserGroups(userGroups.filter((group) => group.usrGrp !== usrGrp));
console.log("User group deleted successfully:", usrGrp);
} else {
console.error("Failed to delete user group:", response.statusText);
}
} catch (error) {
console.error("Error deleting user group:", error);
}
};
const handleModal = () => {
setIsModalOpen(true);
};
const columns = [
{
field: "usrGrp",
headerName: "User Group ID",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "groupName",
headerName: "Group Name",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "groupDesc",
headerName: "Group Description",
width: 150,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "createby",
headerName: "Create By",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "createdate",
headerName: "Create Date",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "updatedate",
headerName: "Update Date",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "updateby",
headerName: "Update By",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "status",
headerName: "Status",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "grouplevel",
headerName: "Group Level",
width: 100,
headerClassName: "custom-header",
cellClassName: "custom-cell",
},
{
field: "actions",
headerName: "Actions",
width: 150,
renderCell: ({ row }) => (
<div style={{ position: 'relative' }}>
<div
className="three-dots"
onClick={() => handleThreeDotsClick(row.usrGrp)}
>
<FontAwesomeIcon icon={faEllipsisV} />
</div>
{selectedUserGroup === row.usrGrp && (
<div className="popover">
<button onClick={() => handleDelete(row.usrGrp)}>Delete</button>
</div>
)}
</div>
),
},
];
return (
<div className="user-group-maintenance-container">
<Typography variant="h4" className="main-title" gutterBottom>
User Group Maintenance
</Typography>
<Box className="data-grid-container" sx={{ height: 500, width: '100%' }}>
<DataGrid
rows={userGroups}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleModal={handleModal}
/>
),
}}
pageSize={10}
rowsPerPageOptions={[10]}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
/>
</Box>
{isModalOpen && (
<div className="modal-overlay">
<div className="modal-content">
<Typography variant="h5" className="modal-title">
Add New User Group
</Typography>
{/* Modal form content here */}
<div className="modal-actions">
<Button
variant="contained"
onClick={() => setIsModalOpen(false)}
className="toolbar-button"
>
Close
</Button>
</div>
</div>
</div>
)}
</div>
);
}
export default UserMaintance;

View File

@@ -1,132 +1,132 @@
/* .container {
font-family: Arial, sans-serif;
max-width: 2000px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 0 auto;
max-width: 2000px;
}
table th,
table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
table th {
background-color: #f2f2f2;
}
input[type='text'] {
width: 100%;
padding: 5px;
}
button {
padding: 8px 12px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
label {
margin-right: 10px;
}
select {
padding: 5px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.modal-content input {
margin-bottom: 10px;
}
.modal-content button {
margin-top: 10px;
}
.update-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.update-modal-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.update-modal-content input {
margin-bottom: 10px;
}
.update-modal-content button {
margin-top: 10px;
}
.spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #7983ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
} */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}
/* .container {
font-family: Arial, sans-serif;
max-width: 2000px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 0 auto;
max-width: 2000px;
}
table th,
table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
table th {
background-color: #f2f2f2;
}
input[type='text'] {
width: 100%;
padding: 5px;
}
button {
padding: 8px 12px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
label {
margin-right: 10px;
}
select {
padding: 5px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.modal-content input {
margin-bottom: 10px;
}
.modal-content button {
margin-top: 10px;
}
.update-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.update-modal-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.update-modal-content input {
margin-bottom: 10px;
}
.update-modal-content button {
margin-top: 10px;
}
.spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #7983ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
} */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
.custom-header, .custom-cell {
font-family: 'PT Serif", serif ';
font-style: normal;
font-weight: bold;
}

View File

@@ -1,168 +1,499 @@
import React, { useState, useEffect, useRef } from "react";
import { Box, Button } from "@mui/material";
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
import { BsThreeDotsVertical } from "react-icons/bs";
import Modal from "./Modal"; // Import your Modal component
import UpdateModal from "./UpdateModal"; // Import your UpdateModal component
import "./UserMaintance.css";
const api = process.env.REACT_APP_API_BASE_URL;
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
const handleGoToPage1 = () => {
if (apiRef.current) {
apiRef.current.setPage(1);
}
};
return (
<GridToolbarContainer>
<Button onClick={handleGoToPage1}>Go to page 1</Button>
<Button onClick={handleModal}>+</Button>
</GridToolbarContainer>
);
}
function UserMaintance() {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
const [selectedUserForUpdate, setSelectedUserForUpdate] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false);
const [newUser, setNewUser] = useState({
userId: "",
username: "",
fullName: "",
email: "",
usrGrpName: "",
});
const apiRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("token");
console.log("object", token);
try {
const response = await fetch(`${api}/api/getAllAppUser`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
const usersWithIds = data.map((user, index) => ({
...user,
id: index + 1,
}));
setUsers(usersWithIds);
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData();
}, []);
const handleThreeDotsClick = (userId) => {
setSelectedUser(userId === selectedUser ? null : userId);
};
const handleDelete = (userId) => {
console.log("Delete user with ID:", userId);
};
const handleUpdate = (user) => {
setSelectedUserForUpdate(user);
setIsUpdateModalOpen(true);
};
const handleModal = () => {
setIsModalOpen(true);
};
const handleModalSave = (data) => {
setUsers((prevUsers) => [
...prevUsers,
{ ...data, id: prevUsers.length + 1 },
]);
setIsModalOpen(false);
};
const handleUpdateSave = () => {
setIsUpdateModalOpen(false);
};
const columns = [
{ field: "userId", headerName: "User ID", width: 200 },
{ field: "username", headerName: "Username", width: 200 },
{ field: "fullName", headerName: "Full Name", width: 200 },
{ field: "email", headerName: "Email", width: 200 },
{ field: "usrGrpName", headerName: "User Group", width: 150 },
{
field: "actions",
headerName: "Actions",
width: 100,
renderCell: ({ row }) => (
<div>
<div
className="three-dots"
onClick={() => handleThreeDotsClick(row.userId)}
>
<BsThreeDotsVertical />
</div>
{selectedUser === row.userId && (
<div className="popover">
<button onClick={() => handleDelete(row.userId)}>Delete</button>
<button onClick={() => handleUpdate(row)}>Update</button>
</div>
)}
</div>
),
},
];
return (
<Box
sx={{ height: "calc(100vh - 150px)", width: "100%", overflowX: "auto" }}
>
<div className="text-center text-3xl text-white bg-gray-400">
User Maintenance
</div>
<DataGrid
className="bg-gray-400"
rows={users}
columns={columns}
components={{
Toolbar: () => (
<CustomToolbar
apiRef={apiRef}
handleThreeDotsClick={handleThreeDotsClick}
handleModal={handleModal}
/>
),
}}
pageSize={10}
onGridReady={(gridApi) => {
apiRef.current = gridApi;
}}
/>
{isModalOpen && (
<Modal
setNewUser={setNewUser}
newUser={newUser}
onSave={handleModalSave}
onClose={() => setIsModalOpen(false)}
/>
)}
{isUpdateModalOpen && (
<UpdateModal
user={selectedUserForUpdate}
onUpdate={handleUpdateSave}
onClose={() => setIsUpdateModalOpen(false)}
/>
)}
</Box>
);
}
export default UserMaintance;
import React, { useState, useEffect } from "react";
import {
Box,
Button,
TextField,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Menu,
MenuItem,
Checkbox,
FormControlLabel,
Pagination,
Modal,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
FormGroup,
Switch,
Tooltip,
Typography,
InputAdornment,
Select,
FormControl,
InputLabel,
CircularProgress,
} from "@mui/material";
import {
Add,
Search,
Settings,
Edit,
Delete,
Download,
Upload,
Close,
Visibility,
VisibilityOff,
Check,
} from "@mui/icons-material";
import { toast } from "react-toastify";
import * as XLSX from "xlsx";
import { getAllUsers, createUser, updateUser, deleteUser } from "../../ApiServices/Usermaintenanceapi";
function UserMaintenanceView() {
// State management
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const [columnMenuAnchor, setColumnMenuAnchor] = useState(null);
// User data state
const [userData, setUserData] = useState({
userId: "",
username: "",
fullName: "",
email: "",
mob_no: "",
active: true,
usrGrpId: "",
});
// Column visibility configuration
const [visibleColumns, setVisibleColumns] = useState({
userId: true,
username: true,
fullName: true,
email: true,
mob_no: true,
active: true,
usrGrpId: true,
actions: true,
});
// Fetch users on component mount
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const response = await getAllUsers();
setUsers(response.data || []);
} catch (error) {
console.error("Error fetching users:", error);
toast.error("Failed to fetch users");
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
// Filter users based on search query
const filteredUsers = users.filter((user) =>
user.username.toLowerCase().includes(searchQuery.toLowerCase())
);
// Pagination logic
const totalPages = Math.ceil(filteredUsers.length / recordsPerPage);
const paginatedUsers = filteredUsers.slice(
(currentPage - 1) * recordsPerPage,
currentPage * recordsPerPage
);
// Column toggle handler
const toggleColumn = (column) => {
setVisibleColumns((prev) => ({
...prev,
[column]: !prev[column],
}));
};
// Modal handlers
const handleOpenModal = (user = null) => {
if (user) {
setUserData(user);
setIsEditing(true);
} else {
setUserData({
userId: "",
username: "",
fullName: "",
email: "",
mob_no: "",
active: true,
usrGrpId: "",
});
setIsEditing(false);
}
setIsModalOpen(true);
};
const handleCloseModal = () => {
setIsModalOpen(false);
};
// Form input handler
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setUserData((prev) => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
};
// Form submission handler
const handleSubmit = async (e) => {
e.preventDefault();
try {
if (isEditing) {
await updateUser(userData);
toast.success("User updated successfully");
} else {
await createUser(userData);
toast.success("User added successfully");
}
// Refresh user list
const response = await getAllUsers();
setUsers(response.data || []);
handleCloseModal();
} catch (error) {
console.error("Error saving user:", error);
toast.error("There was an error while submitting the USER.");
}
};
// Delete user handler
const handleDelete = async (userId) => {
try {
await deleteUser(userId);
setUsers(users.filter((user) => user.userId !== userId));
toast.success("User deleted successfully");
} catch (error) {
console.error("Error deleting user:", error);
toast.error("Failed to delete user");
}
};
// Excel export handler
const exportToExcel = () => {
const worksheet = XLSX.utils.json_to_sheet(users);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "UserDetails");
XLSX.writeFile(workbook, "UserMaintenance.xlsx");
};
// Excel import handlers
const handleFileChange = (e) => {
// Implement your file import logic here
};
const handleImport = () => {
// Implement your import logic here
toast.success("File imported successfully");
setIsImportModalOpen(false);
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="80vh">
<CircularProgress />
</Box>
);
}
return (
<Box sx={{ p: 3, marginTop: "7rem" }}>
{/* Header and Search */}
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
<Typography variant="h4">User Maintenance</Typography>
<Box sx={{ display: "flex", gap: 2 }}>
<TextField
size="small"
placeholder="Search"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
}}
/>
<Tooltip title="Download template">
<IconButton onClick={exportToExcel}>
<Download />
</IconButton>
</Tooltip>
<Tooltip title="Import users">
<IconButton onClick={() => setIsImportModalOpen(true)}>
<Upload />
</IconButton>
</Tooltip>
<Button
variant="contained"
startIcon={<Add />}
onClick={() => handleOpenModal()}
>
Add User
</Button>
</Box>
</Box>
{/* Table */}
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
{Object.entries(visibleColumns).map(
([key, visible]) =>
visible && (
<TableCell key={key}>
{key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " ")}
</TableCell>
)
)}
</TableRow>
</TableHead>
<TableBody>
{paginatedUsers.length === 0 ? (
<TableRow>
<TableCell colSpan={Object.keys(visibleColumns).length} align="center">
No users found
</TableCell>
</TableRow>
) : (
paginatedUsers.map((user) => (
<TableRow key={user.userId}>
{Object.entries(visibleColumns).map(([key, visible]) => {
if (!visible) return null;
if (key === "actions") {
return (
<TableCell key={key}>
<Tooltip title="Edit">
<IconButton onClick={() => handleOpenModal(user)}>
<Edit color="primary" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton onClick={() => handleDelete(user.userId)}>
<Delete color="error" />
</IconButton>
</Tooltip>
</TableCell>
);
} else if (key === "active") {
return (
<TableCell key={key}>
<Switch
checked={user.active}
readOnly
color="primary"
/>
</TableCell>
);
} else {
return <TableCell key={key}>{user[key]}</TableCell>;
}
})}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination and Column Controls */}
<Box sx={{ display: "flex", justifyContent: "space-between", mt: 2 }}>
<Box>
<Button
onClick={(e) => setColumnMenuAnchor(e.currentTarget)}
startIcon={<Settings />}
>
Manage Columns
</Button>
<Menu
anchorEl={columnMenuAnchor}
open={Boolean(columnMenuAnchor)}
onClose={() => setColumnMenuAnchor(null)}
>
{Object.keys(visibleColumns).map((column) => (
<MenuItem key={column}>
<FormControlLabel
control={
<Checkbox
checked={visibleColumns[column]}
onChange={() => toggleColumn(column)}
/>
}
label={column.charAt(0).toUpperCase() + column.slice(1).replace(/_/g, " ")}
/>
</MenuItem>
))}
</Menu>
</Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<FormControl size="small" sx={{ minWidth: 120 }}>
<InputLabel>Rows per page</InputLabel>
<Select
value={recordsPerPage}
label="Rows per page"
onChange={(e) => {
setRecordsPerPage(e.target.value);
setCurrentPage(1);
}}
>
{[5, 10, 25, 50].map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
<Pagination
count={totalPages}
page={currentPage}
onChange={(e, page) => setCurrentPage(page)}
color="primary"
/>
</Box>
</Box>
{/* Add/Edit User Modal */}
<Dialog open={isModalOpen} onClose={handleCloseModal} fullWidth maxWidth="sm">
<DialogTitle>
{isEditing ? "Edit User" : "Add New User"}
<IconButton
onClick={handleCloseModal}
sx={{
position: "absolute",
right: 8,
top: 8,
}}
>
<Close />
</IconButton>
</DialogTitle>
<DialogContent>
<form onSubmit={handleSubmit}>
<TextField
margin="normal"
fullWidth
label="Username"
name="username"
value={userData.username}
onChange={handleInputChange}
required
/>
<TextField
margin="normal"
fullWidth
label="Full Name"
name="fullName"
value={userData.fullName}
onChange={handleInputChange}
required
/>
<TextField
margin="normal"
fullWidth
label="Email"
name="email"
type="email"
value={userData.email}
onChange={handleInputChange}
required
/>
<TextField
margin="normal"
fullWidth
label="Mobile Number"
name="mob_no"
value={userData.mob_no}
onChange={handleInputChange}
/>
<TextField
margin="normal"
fullWidth
label="User Group"
name="usrGrpId"
value={userData.usrGrpId}
onChange={handleInputChange}
/>
<FormGroup>
<FormControlLabel
control={
<Switch
name="active"
checked={userData.active}
onChange={handleInputChange}
/>
}
label="Active"
/>
</FormGroup>
</form>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseModal}>Cancel</Button>
<Button onClick={handleSubmit} variant="contained">
{isEditing ? "Save Changes" : "Add User"}
</Button>
</DialogActions>
</Dialog>
{/* Import Modal */}
<Dialog open={isImportModalOpen} onClose={() => setIsImportModalOpen(false)}>
<DialogTitle>
Import Users
<IconButton
onClick={() => setIsImportModalOpen(false)}
sx={{
position: "absolute",
right: 8,
top: 8,
}}
>
<Close />
</IconButton>
</DialogTitle>
<DialogContent>
<input
type="file"
accept=".xlsx, .xls, .csv"
onChange={handleFileChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setIsImportModalOpen(false)}>Cancel</Button>
<Button onClick={handleImport} variant="contained">
Import
</Button>
</DialogActions>
</Dialog>
</Box>
);
}
export default UserMaintenanceView;

View File

@@ -1,126 +1,123 @@
/* Global Styles */
body {
font-family: 'Roboto', sans-serif;
background-color: #f4f6f8;
color: #333;
margin: 0;
padding: 0;
}
.dashboard {
display: flex;
flex-direction: column;
height: 100vh;
}
/* Horizontal Navbar */
.horizontal-navbar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #2c3e50;
color: #ecf0f1;
padding: 10px 20px;
}
.horizontal-navbar h3 {
margin: 0;
font-size: 24px;
}
.nav {
display: flex;
gap: 20px;
}
.nav-link {
color: #ecf0f1;
text-decoration: none;
font-size: 18px;
transition: color 0.3s;
}
.nav-link:hover {
color: #3498db;
}
button {
background-color: #e74c3c;
color: #ecf0f1;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #c0392b;
}
/* Content */
.content {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar-content-wrapper {
display: flex;
flex: 1;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 250px;
background-color: #34495e;
color: #ecf0f1;
transition: width 0.3s;
overflow: auto;
}
.sidebar.collapsed {
width: 80px;
}
.sidebar-content-wrapper .sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-content-wrapper .sidebar ul li {
padding: 15px 20px;
cursor: pointer;
transition: background-color 0.3s;
}
.sidebar-content-wrapper .sidebar ul li:hover {
background-color: #2c3e50;
}
.sidebar-content-wrapper .sidebar ul li.active {
background-color: #3498db;
}
.sidebar-content-wrapper .sidebar .toggle-btn {
text-align: center;
padding: 10px;
cursor: pointer;
}
/* Main Content */
.main-content {
flex: 1;
padding: 20px;
background-color: #ecf0f1;
overflow-y: auto;
}
.main-content h3 {
margin-top: 0;
}
/* Global Styles */
body {
font-family: 'Roboto', sans-serif;
background-color: #f4f6f8;
color: #333;
margin: 0;
padding: 0;
}
.dashboard {
display: flex;
flex-direction: column;
height: 100vh;
}
/* Horizontal Navbar */
.horizontal-navbar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #2c3e50;
color: #ecf0f1;
padding: 10px 20px;
}
.horizontal-navbar h3 {
margin: 0;
font-size: 24px;
}
.nav {
display: flex;
gap: 20px;
}
.nav-link {
color: #ecf0f1;
text-decoration: none;
font-size: 18px;
transition: color 0.3s;
}
.nav-link:hover {
color: #3498db;
}
button {
background-color: #e74c3c;
color: #ecf0f1;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #c0392b;
}
/* Content */
.content {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar-content-wrapper {
display: flex;
flex: 1;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 250px;
background-color: #34495e;
color: #ecf0f1;
transition: width 0.3s;
overflow: auto;
}
.sidebar.collapsed {
width: 80px;
}
.sidebar-content-wrapper .sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-content-wrapper .sidebar ul li {
padding: 15px 20px;
cursor: pointer;
transition: background-color 0.3s;
}
.sidebar-content-wrapper .sidebar ul li:hover {
background-color: #2c3e50;
}
.sidebar-content-wrapper .sidebar ul li.active {
background-color: #3498db;
}
.sidebar-content-wrapper .sidebar .toggle-btn {
text-align: center;
padding: 10px;
cursor: pointer;
}
/* Main Content */
.main-content {
flex: 1;
padding: 20px;
background-color: #ecf0f1;
overflow-y: auto;
}
.main-content h3 {
margin-top: 0;
}

View File

@@ -1,202 +1,606 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { useState, useEffect } from "react";
import Sidebar from "./sidebar";
import { useNavigate } from "react-router-dom";
import UserMaintanceComponent from "./UserMaintance";
import UserGroupMaintanceComponent from "./UserGroupMaintance/UserGroupMaintance";
import MenuMaintanceComponent from "./MenuMaintance/MenuMaintance";
import MenuAccessControlComponent from "./MenuAccessControl/MenuAccessControl";
import SystemParametersComponent from "./SystemParameters/SystemParameters";
// import AccessTypeComponent from "./AccessType/AccessType";
import ApiRegistery from "./ApiRegistery/ApiRegistery";
import TokenRegistery from "./TokenRegistery/TokenRegistery";
import HomePage from "./HomePage";
import Setup from "./Setup.js";
import Report from "./Report";
import { FaCog, FaUsers, FaSignOutAlt, FaHome, FaChartBar } from "react-icons/fa";
const Dashboard = () => {
const navigate = useNavigate();
const [menus, setMenus] = useState([]);
const [selectedUserMaintance, setSelectedUserMaintance] = useState(null);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [content, setContent] = useState("");
useEffect(() => {
const fetchMenusData = async () => {
const token = localStorage.getItem("authtoken");
const apiUrl = `${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`;
console.log("Fetching menus from API:", apiUrl);
try {
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const menuData = await response.json();
setMenus(menuData);
} else {
const errorText = await response.text();
console.error(
"Failed to fetch menus. Status:",
response.status,
"Response:",
errorText
);
}
} catch (error) {
console.error("Error during menu fetch:", error);
}
};
fetchMenusData();
}, [navigate]);
const handleMenuItemClick = (menuItem) => {
setSelectedUserMaintance(menuItem);
setContent(menuItem.menuItemDesc); // Update content based on clicked menu item
};
const handleHomeClick = () => {
setSelectedUserMaintance(null);
setContent("Home");
};
const handleSetupClick = () => {
setSelectedUserMaintance(null);
setContent("Setup");
};
const handleReportClick = () => {
setSelectedUserMaintance(null);
setContent("Report");
};
const handleSidebarToggle = () => {
setSidebarCollapsed(!sidebarCollapsed);
};
const handleLogout = () => {
localStorage.removeItem("authToken");
localStorage.removeItem("user");
navigate("/");
};
return (
<div className="flex flex-col h-screen bg-gradient-to-br from-purple-50 via-white to-blue-50">
{/* Top Navigation Bar */}
<div className="flex justify-between items-center bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-4 shadow-lg">
<div className="flex items-center space-x-4">
<h3 className="text-2xl font-bold">Dashboard</h3>
<div className="h-6 w-px bg-white/30"></div>
<nav className="flex space-x-6">
<button
onClick={handleHomeClick}
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
>
<FaHome className="w-5 h-5" />
<span>Home</span>
</button>
<button
onClick={handleSetupClick}
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
>
<FaCog className="w-5 h-5" />
<span>Setup</span>
</button>
<button
onClick={handleReportClick}
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
>
<FaChartBar className="w-5 h-5" />
<span>Reports</span>
</button>
</nav>
</div>
<button
className="flex items-center space-x-2 bg-white/10 hover:bg-white/20 text-white font-semibold py-2 px-4 rounded-lg transition-all duration-200"
onClick={handleLogout}
>
<FaSignOutAlt className="w-5 h-5" />
<span>Logout</span>
</button>
</div>
<div className="flex flex-1 overflow-hidden">
{/* Sidebar */}
<div
className={`bg-gradient-to-b from-purple-700 to-indigo-800 text-white transition-all duration-300 ${
sidebarCollapsed ? "w-16" : "w-64"
} min-w-16 shadow-xl`}
>
<div className="flex justify-end p-2">
<button
onClick={handleSidebarToggle}
className="text-white/80 hover:text-white bg-white/10 hover:bg-white/20 p-2 rounded-lg transition-all duration-200"
>
{sidebarCollapsed ? ">>" : "<<"}
</button>
</div>
<Sidebar
menus={menus}
handleMenuItemClick={handleMenuItemClick}
collapsed={sidebarCollapsed}
setCollapsed={setSidebarCollapsed}
/>
</div>
{/* Main Content Area */}
<div className="flex-1 p-6 overflow-auto bg-gradient-to-br from-purple-50 via-white to-blue-50">
<div className="max-w-7xl mx-auto">
{content === "Setup" ? (
<div className="bg-white rounded-xl shadow-lg p-6">
<Setup />
</div>
) : content === "Home" ? (
<div className="bg-white rounded-xl shadow-lg p-6">
<HomePage />
</div>
) : content === "Report" ? (
<div className="bg-white rounded-xl shadow-lg p-6">
<Report />
</div>
) : selectedUserMaintance ? (
<div className="bg-white rounded-xl shadow-lg p-6">
<h3 className="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b border-purple-100">
{selectedUserMaintance.menuItemDesc}
</h3>
{selectedUserMaintance.menuItemDesc === "User Maintance" ? (
<UserMaintanceComponent />
) : selectedUserMaintance.menuItemDesc === "User Group Maintance" ? (
<UserGroupMaintanceComponent />
) : selectedUserMaintance.menuItemDesc === "Menu Maintance" ? (
<MenuMaintanceComponent />
) : selectedUserMaintance.menuItemDesc === "Menu Access Control" ? (
<MenuAccessControlComponent />
) : selectedUserMaintance.menuItemDesc === "System Parameters" ? (
<SystemParametersComponent />
) : selectedUserMaintance.menuItemDesc === "Api Registery" ? (
<ApiRegistery />
) : selectedUserMaintance.menuItemDesc === "Token Registery" ? (
<TokenRegistery />
) : null}
</div>
) : (
<div className="bg-white rounded-xl shadow-lg p-6">
<HomePage />
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Dashboard;
import React, { useState, useEffect } from "react";
import { useNavigate, Link, Routes, Route, Outlet } from "react-router-dom";
import {
Box,
Divider,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Collapse,
useTheme,
styled,
Menu,
MenuItem,
Avatar,
Typography,
Tooltip
} from '@mui/material';
import {
ChevronLeft,
ChevronRight,
ExpandMore,
ExpandLess,
Description as DescriptionIcon,
Error as ErrorIcon,
Storage as StorageIcon,
SwapHoriz as SwapHorizIcon,
Home as HomeIcon,
Settings as SettingsIcon,
GridView as GridViewIcon,
BarChart as BarChartIcon,
Apps as AppsIcon,
Person as PersonIcon,
Language as LanguageIcon,
LockReset as LockResetIcon,
Logout as LogoutIcon,
Info as InfoIcon,
Menu as MenuIcon
} from '@mui/icons-material';
import UserMaintanceComponent from "./UserMaintance";
import UserGroupMaintanceComponent from "./UserGroupMaintance/UserGroupMaintance";
import MenuMaintanceComponent from "./MenuMaintance/MenuMaintance";
import MenuAccessControlComponent from "./MenuAccessControl/MenuAccessControl";
import SystemParametersComponent from "./SystemParameters/SystemParameters";
import AccessTypeComponent from "./AccessType/AccessType";
import ApiRegistery from "./ApiRegistery/ApiRegistery";
import TokenRegistery from "./TokenRegistery/TokenRegistery";
import HomePage from "./HomePage";
import Setup from "./Setup";
import Report from "./reports/Report";
import SequenceGenerator from "./document sequence/sequencegenerator";
import About from "./dropdown/about";
import Profile from "./dropdown/profile";
import DashboardRunnerAll from "./../dashboardnew/dashboardrunner/dashboardrunnerall";
import DashboardNewAll from "../dashboardnew/dashboardbuildernewall";
import DashboardNewAdd from "../dashboardnew/dashboardadd/dashboardbuilderadd";
import DashboardNewEdit from "../dashboardnew/editdashboard/editformdashboard";
import EditNewDash from "../dashboardnew/editdashboard/editdashboard";
import DashboardRunner from "../dashboardnew/dashboardrunner/dashboardrunner";
import SubMenuMaintenance from "./sub menu/submenumaintanence";
const Dashboard = () => {
const navigate = useNavigate();
const theme = useTheme();
const [menus, setMenus] = useState([]);
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
const [openTransaction, setOpenTransaction] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const [submenuAnchorEl, setSubmenuAnchorEl] = useState(null);
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
const drawerWidth = 220;
const collapsedWidth = 60;
useEffect(() => {
const user = JSON.parse(localStorage.getItem("user"));
setCurrentUser(user);
const fetchMenusData = async () => {
const token = localStorage.getItem("token");
const apiUrl = `${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`;
console.log("Fetching menus from API:", apiUrl);
try {
const response = await fetch(apiUrl, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const menuData = await response.json();
setMenus(menuData);
} else {
const errorText = await response.text();
console.error(
"Failed to fetch menus. Status:",
response.status,
"Response:",
errorText
);
}
} catch (error) {
console.error("Error during menu fetch:", error);
}
};
fetchMenusData();
}, [navigate]);
const handleHomeClick = () => {
navigate('/dashboard');
};
const handleSetupClick = () => {
navigate('/dashboard/setup');
};
const handleReportClick = () => {
navigate('/dashboard/reports');
};
const handleMenuItemClick = (menu) => {
const routeMap = {
"User Maintance": "/dashboard/setup/user-maintenance",
"User Group Maintance": "/dashboard/setup/user-group-maintenance",
"Menu Maintance": "/dashboard/setup/menu-maintenance",
"Menu Access Control": "/dashboard/setup/menu-access-control",
"System Parameters": "/dashboard/setup/system-parameters",
"Access Type": "/dashboard/setup/access-type",
"Api Registery": "/dashboard/setup/api-registry",
"Token Registery": "/dashboard/setup/token-registry",
"sequence generator": "/dashboard/setup/document-sequence",
};
if (routeMap[menu.menuItemDesc]) {
navigate(routeMap[menu.menuItemDesc]);
}
};
const handleSidebarToggle = () => {
setSidebarCollapsed(!sidebarCollapsed);
};
const handleTransactionToggle = () => {
setOpenTransaction(!openTransaction);
};
const handleMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleSubmenuOpen = (event) => {
setSubmenuAnchorEl(event.currentTarget);
};
const handleSubmenuClose = () => {
setSubmenuAnchorEl(null);
};
const handleLogout = () => {
localStorage.removeItem("authToken");
localStorage.removeItem("user");
navigate("/");
handleMenuClose();
};
const handleLanguageSelect = (lang) => {
localStorage.setItem("appLanguage", lang);
handleSubmenuClose();
handleMenuClose();
window.location.reload();
};
const toggleMobileSidebar = () => {
setMobileSidebarOpen(!mobileSidebarOpen);
};
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
}));
return (
<div className="flex flex-col h-screen">
{/* Navbar - fixed at the top */}
<div className="h-16 flex-shrink-0 bg-gray-800 text-white p-4 z-10">
<div className="flex justify-between items-center h-full">
<div className="flex items-center">
<IconButton
color="inherit"
onClick={toggleMobileSidebar}
sx={{ display: { xs: 'block', lg: 'none' }, mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Dashboard
</Typography>
</div>
{/* Desktop Navigation Icons */}
<Box sx={{ display: { xs: 'none', lg: 'flex' }, gap: 2 }}>
<Tooltip title="Home">
<IconButton color="inherit" onClick={handleHomeClick}>
<HomeIcon />
</IconButton>
</Tooltip>
<Tooltip title="Settings">
<IconButton color="inherit" onClick={handleSetupClick}>
<SettingsIcon />
</IconButton>
</Tooltip>
<Tooltip title="Dashboard">
<IconButton color="inherit" onClick={() => navigate('/dashboard/dashboard-runner-all')}>
<GridViewIcon />
</IconButton>
</Tooltip>
<Tooltip title="Reports">
<IconButton color="inherit" onClick={handleReportClick}>
<BarChartIcon />
</IconButton>
</Tooltip>
</Box>
{/* User Dropdown */}
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Tooltip title="Account settings">
<IconButton
onClick={handleMenuOpen}
size="small"
sx={{ ml: 2 }}
aria-controls={anchorEl ? 'account-menu' : undefined}
aria-haspopup="true"
aria-expanded={anchorEl ? 'true' : undefined}
>
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main' }}>
<PersonIcon fontSize="small" />
</Avatar>
<Typography variant="body1" sx={{ ml: 1, color: 'white', display: { xs: 'none', lg: 'block' } }}>
{currentUser ? currentUser.fullname || currentUser.username || currentUser.email : "Guest"}
</Typography>
</IconButton>
</Tooltip>
</Box>
{/* User Menu */}
<Menu
anchorEl={anchorEl}
id="account-menu"
open={Boolean(anchorEl)}
onClose={handleMenuClose}
onClick={handleMenuClose}
PaperProps={{
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&:before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={() => navigate('/dashboard/about')}>
<ListItemIcon>
<InfoIcon fontSize="small" />
</ListItemIcon>
About
</MenuItem>
<MenuItem onClick={() => navigate('/dashboard/profile')}>
<ListItemIcon>
<PersonIcon fontSize="small" />
</ListItemIcon>
My Profile
</MenuItem>
<MenuItem onClick={() => navigate('/dashboard/dashboard-runner-all')}>
<ListItemIcon>
<GridViewIcon fontSize="small" />
</ListItemIcon>
Activity
</MenuItem>
<MenuItem
onClick={handleSubmenuOpen}
onMouseEnter={handleSubmenuOpen}
>
<ListItemIcon>
<LanguageIcon fontSize="small" />
</ListItemIcon>
Choose Language
{Boolean(submenuAnchorEl) ? <ExpandLess sx={{ ml: 1 }} /> : <ExpandMore sx={{ ml: 1 }} />}
</MenuItem>
<MenuItem onClick={() => navigate('/admin/resetpassword')}>
<ListItemIcon>
<LockResetIcon fontSize="small" />
</ListItemIcon>
Reset Password
</MenuItem>
<Divider />
<MenuItem onClick={handleLogout}>
<ListItemIcon>
<LogoutIcon fontSize="small" />
</ListItemIcon>
Logout
</MenuItem>
</Menu>
{/* Language Submenu */}
<Menu
anchorEl={submenuAnchorEl}
open={Boolean(submenuAnchorEl)}
onClose={handleSubmenuClose}
MenuListProps={{
onMouseLeave: handleSubmenuClose,
'aria-labelledby': 'language-button',
}}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem onClick={() => handleLanguageSelect("en")}>English</MenuItem>
<MenuItem onClick={() => handleLanguageSelect("hi")}>Hindi</MenuItem>
<MenuItem onClick={() => handleLanguageSelect("ta")}>Tamil</MenuItem>
<MenuItem onClick={() => handleLanguageSelect("te")}>Telugu</MenuItem>
</Menu>
</div>
</div>
{/* Mobile Sidebar */}
<Drawer
variant="temporary"
open={mobileSidebarOpen}
onClose={toggleMobileSidebar}
ModalProps={{
keepMounted: true,
}}
sx={{
display: { xs: 'block', lg: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: 240 },
}}
>
<Box sx={{ p: 2 }}>
<List>
<ListItemButton onClick={() => { handleHomeClick(); toggleMobileSidebar(); }}>
<ListItemIcon><HomeIcon /></ListItemIcon>
<ListItemText primary="Home" />
</ListItemButton>
<ListItemButton onClick={() => { handleSetupClick(); toggleMobileSidebar(); }}>
<ListItemIcon><SettingsIcon /></ListItemIcon>
<ListItemText primary="Settings" />
</ListItemButton>
<ListItemButton onClick={() => { navigate('/dashboard/dashboard-runner-all'); toggleMobileSidebar(); }}>
<ListItemIcon><GridViewIcon /></ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItemButton>
<ListItemButton onClick={() => { handleReportClick(); toggleMobileSidebar(); }}>
<ListItemIcon><BarChartIcon /></ListItemIcon>
<ListItemText primary="Reports" />
</ListItemButton>
</List>
</Box>
</Drawer>
{/* Main content area below navbar */}
<div className="flex flex-1 overflow-hidden">
{/* Integrated Sidebar */}
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', lg: 'block' },
width: sidebarCollapsed ? collapsedWidth : drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: sidebarCollapsed ? collapsedWidth : drawerWidth,
boxSizing: 'border-box',
backgroundColor: theme.palette.grey[200],
borderRight: '1px solid rgba(0, 0, 0, 0.12)',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
height: 'calc(100vh - 64px)',
position: 'relative',
},
}}
>
<DrawerHeader>
<IconButton onClick={handleSidebarToggle} size="small">
{sidebarCollapsed ? <ChevronRight /> : <ChevronLeft />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{/* Transaction with submenus */}
<ListItem disablePadding>
<ListItemButton
onClick={handleTransactionToggle}
sx={{
minHeight: 48,
justifyContent: sidebarCollapsed ? 'center' : 'initial',
px: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: sidebarCollapsed ? 'auto' : 3,
justifyContent: 'center',
color: 'inherit'
}}
>
<SwapHorizIcon />
</ListItemIcon>
{!sidebarCollapsed && (
<>
<ListItemText primary="Transaction" />
{openTransaction ? <ExpandLess /> : <ExpandMore />}
</>
)}
</ListItemButton>
</ListItem>
{!sidebarCollapsed && (
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
<ListItemButton
sx={{ pl: 4 }}
onClick={() => navigate('/admin/regform')}
>
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
<DescriptionIcon />
</ListItemIcon>
<ListItemText primary="Regform" />
</ListItemButton>
<ListItemButton
sx={{ pl: 4 }}
onClick={() => navigate('/admin/error404')}
>
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
<ErrorIcon />
</ListItemIcon>
<ListItemText primary="Additional container" />
</ListItemButton>
</List>
</Collapse>
)}
{/* Masters (single item) */}
<ListItem disablePadding>
<ListItemButton
onClick={() => navigate('/admin/masters')}
sx={{
minHeight: 48,
justifyContent: sidebarCollapsed ? 'center' : 'initial',
px: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: sidebarCollapsed ? 'auto' : 3,
justifyContent: 'center',
color: 'inherit'
}}
>
<StorageIcon />
</ListItemIcon>
{!sidebarCollapsed && (
<ListItemText primary="Masters" />
)}
</ListItemButton>
</ListItem>
{/* Dynamic menu items from API */}
{menus.map((menu) => (
<ListItem key={menu.menuItemId} disablePadding>
<ListItemButton
onClick={() => handleMenuItemClick(menu)}
sx={{
minHeight: 48,
justifyContent: sidebarCollapsed ? 'center' : 'initial',
px: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: sidebarCollapsed ? 'auto' : 3,
justifyContent: 'center',
color: 'inherit'
}}
>
<StorageIcon />
</ListItemIcon>
{!sidebarCollapsed && (
<ListItemText primary={menu.menuItemDesc} />
)}
</ListItemButton>
</ListItem>
))}
</List>
</Drawer>
{/* Content area */}
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: {
xs: '100%',
lg: `calc(100% - ${sidebarCollapsed ? collapsedWidth : drawerWidth}px)`
},
marginLeft: {
xs: 0,
lg: `${sidebarCollapsed ? collapsedWidth : drawerWidth}px`
},
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
height: 'calc(100vh - 64px)',
overflow: 'auto'
}}
>
<Routes>
<Route index element={<HomePage />} />
<Route path="setup" element={<Setup />}>
<Route index element={<div>Select a setup option from the menu</div>} />
<Route path="user-maintenance" element={<UserMaintanceComponent />} />
<Route path="user-group-maintenance" element={<UserGroupMaintanceComponent />} />
<Route path="menu-maintenance" element={<MenuMaintanceComponent />} />
<Route path="menu-access-control" element={<MenuAccessControlComponent />} />
<Route path="system-parameters" element={<SystemParametersComponent />} />
<Route path="access-type" element={<AccessTypeComponent />} />
<Route path="api-registry" element={<ApiRegistery />} />
<Route path="token-registry" element={<TokenRegistery />} />
<Route path="document-sequence" element={<SequenceGenerator />} />
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
</Route>
<Route path="reports" element={<Report />} />
<Route path="about" element={<About/>} />
<Route path="profile" element={<Profile/>} />
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
<Route path="*" element={<div>Page Not Found</div>} />
</Routes>
</Box>
</div>
</div>
);
};
export default Dashboard;

View File

@@ -0,0 +1,409 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
MenuItem,
IconButton,
Menu,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Card,
Alert,
Snackbar,
InputAdornment,
TablePagination,
Divider,
Chip
} from '@mui/material';
import {
MoreVert as MoreVertIcon,
Add as AddIcon,
Edit as EditIcon,
Delete as DeleteIcon,
Close as CloseIcon
} from '@mui/icons-material';
import SequenceGeneratorAPI from './sequencegeneratorapi';
const SequenceGenerator = () => {
const [sequences, setSequences] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
const [editingId, setEditingId] = useState(null);
const [currentSequence, setCurrentSequence] = useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const [openMenuId, setOpenMenuId] = useState(null);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarSeverity, setSnackbarSeverity] = useState('success');
useEffect(() => {
fetchSequences();
}, []);
const fetchSequences = async () => {
setLoading(true);
try {
const data = await SequenceGeneratorAPI.getAll();
setSequences(data);
} catch (err) {
setError(err.response?.data?.message || 'Failed to fetch sequences');
showSnackbar(err.response?.data?.message || 'Failed to fetch sequences', 'error');
} finally {
setLoading(false);
}
};
const showSnackbar = (message, severity) => {
setSnackbarMessage(message);
setSnackbarSeverity(severity);
setSnackbarOpen(true);
};
const handleCreate = () => {
setCurrentSequence(null);
setEditingId(null);
setOpenDialog(true);
};
const handleEdit = (record) => {
setCurrentSequence(record);
setEditingId(record.id);
setOpenDialog(true);
};
const handleDelete = async (id) => {
try {
await SequenceGeneratorAPI.delete(id);
showSnackbar('Sequence deleted successfully', 'success');
fetchSequences();
} catch (err) {
showSnackbar(err.response?.data?.message || 'Failed to delete sequence', 'error');
}
};
const handleMenuClick = (event, id) => {
setAnchorEl(event.currentTarget);
setOpenMenuId(id);
};
const handleMenuClose = () => {
setAnchorEl(null);
setOpenMenuId(null);
};
const handleSubmit = async (event) => {
if (event) {
event.preventDefault();
}
try {
// Get form values directly from state or form inputs
const form = event?.currentTarget || document.querySelector('form');
const formData = new FormData(form);
const values = Object.fromEntries(formData.entries());
// Convert number fields from strings to numbers
values.starting_no = Number(values.starting_no);
values.current_no = Number(values.current_no);
if (editingId) {
await SequenceGeneratorAPI.update(editingId, values);
showSnackbar('Sequence updated successfully', 'success');
} else {
await SequenceGeneratorAPI.create(values);
showSnackbar('Sequence created successfully', 'success');
}
setOpenDialog(false);
fetchSequences();
} catch (err) {
showSnackbar(err.response?.data?.message || 'Operation failed', 'error');
}
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const columns = [
{ id: 'sequence_name', label: 'Name' },
{ id: 'sequence_code', label: 'Sequence Code' },
{ id: 'current_no', label: 'Current No' },
{ id: 'demonstration', label: 'Demonstration' },
{ id: 'actions', label: 'Actions' }
];
return (
<Box sx={{ p: 3 }}>
{/* Error Alert */}
{error && (
<Alert
severity="error"
sx={{ mb: 2 }}
onClose={() => setError(null)}
>
{error}
</Alert>
)}
{/* Success/Error Snackbar */}
<Snackbar
open={snackbarOpen}
autoHideDuration={6000}
onClose={() => setSnackbarOpen(false)}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Alert
onClose={() => setSnackbarOpen(false)}
severity={snackbarSeverity}
sx={{ width: '100%' }}
>
{snackbarMessage}
</Alert>
</Snackbar>
{/* Header and Add Button */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h4" component="h2">
Document Sequence
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleCreate}
>
Add Sequence
</Button>
</Box>
{/* Table */}
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
<Table>
<TableHead sx={{ backgroundColor: 'primary.main' }}>
<TableRow>
{columns.map((column) => (
<TableCell key={column.id} sx={{ color: 'common.white', fontWeight: 'bold' }}>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{sequences
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => (
<TableRow key={row.id} hover>
<TableCell>{row.sequence_name}</TableCell>
<TableCell>{row.sequence_code}</TableCell>
<TableCell>{row.current_no}</TableCell>
<TableCell>
<Chip
label={row.demonstration}
color="primary"
variant="outlined"
/>
</TableCell>
<TableCell>
<IconButton
aria-label="more"
aria-controls={`menu-${row.id}`}
aria-haspopup="true"
onClick={(e) => handleMenuClick(e, row.id)}
>
<MoreVertIcon />
</IconButton>
<Menu
id={`menu-${row.id}`}
anchorEl={anchorEl}
open={openMenuId === row.id}
onClose={handleMenuClose}
>
<MenuItem onClick={() => { handleEdit(row); handleMenuClose(); }}>
<EditIcon sx={{ mr: 1 }} /> Edit
</MenuItem>
<MenuItem
onClick={() => {
handleDelete(row.id);
handleMenuClose();
}}
sx={{ color: 'error.main' }}
>
<DeleteIcon sx={{ mr: 1 }} /> Delete
</MenuItem>
</Menu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={sequences.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
{/* Add/Edit Dialog */}
<Dialog
open={openDialog}
onClose={() => setOpenDialog(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>
{editingId ? 'Update Sequence Generator' : 'Add New Sequence'}
<IconButton
aria-label="close"
onClick={() => setOpenDialog(false)}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent dividers>
{currentSequence && (
<Card variant="outlined" sx={{ mb: 3 }}>
<Box sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Sequence Details
</Typography>
<Divider sx={{ mb: 2 }} />
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex' }}>
<Typography variant="subtitle1" sx={{ width: '30%', fontWeight: 'bold' }}>
Sequence ID
</Typography>
<Typography variant="body1">{currentSequence.id}</Typography>
</Box>
<Box sx={{ display: 'flex' }}>
<Typography variant="subtitle1" sx={{ width: '30%', fontWeight: 'bold' }}>
Demonstration
</Typography>
<Chip
label={currentSequence.demonstration}
color="primary"
sx={{ fontWeight: 'bold' }}
/>
</Box>
</Box>
</Box>
</Card>
)}
<Box component="form" id="sequence-form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
margin="normal"
fullWidth
label="Name*"
name="sequence_name"
defaultValue={currentSequence?.sequence_name || ''}
required
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Sequence Code"
name="sequence_code"
defaultValue={currentSequence?.sequence_code || ''}
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Prefix*"
name="prefix"
defaultValue={currentSequence?.prefix || ''}
required
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Suffix"
name="suffix"
defaultValue={currentSequence?.suffix || ''}
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Separator"
name="seperator"
defaultValue={currentSequence?.seperator || ''}
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Starting No"
name="starting_no"
type="number"
defaultValue={currentSequence?.starting_no || 1}
InputProps={{ inputProps: { min: 1 } }}
sx={{ mb: 2 }}
/>
<TextField
margin="normal"
fullWidth
label="Current No"
name="current_no"
type="number"
defaultValue={currentSequence?.current_no || 1}
InputProps={{ inputProps: { min: 1 } }}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}>Cancel</Button>
<Button
variant="contained"
type="submit"
form="sequence-form" // Associate with the form
>
{editingId ? 'Update' : 'Create'}
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default SequenceGenerator;

View File

@@ -0,0 +1,104 @@
import axios from 'axios';
import { getToken } from '../../../utils/tokenService.js';
// Use REACT_APP_API_BASE_URL instead of REACT_APP_API_URL
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080'; // Default to localhost if not set
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
});
// Request interceptor
api.interceptors.request.use((config) => {
const token = getToken();
console.log('[API] Current token:', token);
console.log('[API] Making request to:', config.url);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
} else {
console.warn('No authToken found for request!');
}
return config;
}, (error) => {
console.error('[API] Request error:', error);
return Promise.reject(error);
});
// Response interceptor
api.interceptors.response.use((response) => {
console.log('[API] Response received:', {
status: response.status,
url: response.config.url,
data: response.data
});
return response;
}, (error) => {
console.error('[API] Response error:', {
status: error.response?.status,
url: error.config?.url,
message: error.message,
response: error.response?.data
});
return Promise.reject(error);
});
const SequenceGeneratorAPI = {
getAll: async () => {
try {
console.log('[API] Fetching all sequences');
const response = await api.get('/sureserve/sequence/seq');
return response.data;
} catch (error) {
console.error('[API] Error fetching sequences:', error);
throw error;
}
},
getById: async (id) => {
try {
console.log(`[API] Fetching sequence with ID: ${id}`);
const response = await api.get(`/sureserve/sequence/seq/${id}`);
return response.data;
} catch (error) {
console.error(`[API] Error fetching sequence ${id}:`, error);
throw error;
}
},
create: async (data) => {
try {
const response = await api.post('/sureserve/sequence/seq', data);
return response.data;
} catch (error) {
console.error('[API] Error creating sequence:', error);
throw error;
}
},
update: async (id, data) => {
try {
const response = await api.put(`/sureserve/sequence/seq/${id}`, data);
return response.data;
} catch (error) {
console.error(`[API] Error updating sequence ${id}:`, error);
throw error;
}
},
delete: async (id) => {
try {
const response = await api.delete(`/sureserve/sequence/seq/${id}`);
return response.data;
} catch (error) {
console.error(`[API] Error deleting sequence ${id}:`, error);
throw error;
}
},
};
export default SequenceGeneratorAPI;

View File

@@ -0,0 +1,53 @@
// Example About component with MUI styling
import React from "react";
import {
Box,
Typography,
CardMedia,
useTheme
} from "@mui/material";
const About = () => {
const theme = useTheme();
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
p: 4,
textAlign: "center"
}}
>
<CardMedia
component="img"
image="/about-image.png"
alt="About Us"
sx={{
width: 300,
height: 200,
objectFit: "cover",
mb: 4,
borderRadius: 1
}}
/>
<Typography variant="h4" component="h1" sx={{ mb: 2, color: theme.palette.primary.main }}>
About Our Application
</Typography>
<Typography variant="body1" sx={{ maxWidth: 600, mb: 2 }}>
This is a comprehensive dashboard application built with React and Material-UI.
</Typography>
<Typography variant="body1" sx={{ maxWidth: 600 }}>
Create new projects for users with appropriate access levels. Contact your administrator
if you need additional permissions.
</Typography>
</Box>
);
};
export default About;

View File

@@ -0,0 +1,207 @@
import React, { useState, useRef } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import {
Container,
Typography,
Avatar,
Button,
TextField,
Divider,
Box,
Stack,
IconButton
} from '@mui/material';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import CancelIcon from '@mui/icons-material/Cancel';
import SaveIcon from '@mui/icons-material/Save';
const Profile = () => {
const navigate = useNavigate();
const [preview, setPreview] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
const fileInputRef = useRef(null);
const handleLogout = () => {
localStorage.removeItem("authToken");
navigate('/auth/login');
};
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
setSelectedFile(file);
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
}
};
const triggerFileInput = () => {
fileInputRef.current.click();
};
const handleCancel = () => {
setPreview(null);
setSelectedFile(null);
fileInputRef.current.value = '';
};
const handleUpload = () => {
if (!selectedFile) return;
alert('Upload functionality would go here');
};
return (
<Container maxWidth="md" sx={{ mt: 4 }}>
<Typography variant="h4" gutterBottom>
My Profile Settings
</Typography>
{/* Profile Photo Section */}
<Box sx={{ mb: 4 }}>
<Typography variant="h6" gutterBottom>
PROFILE PHOTO
</Typography>
<Stack direction="row" alignItems="center" spacing={3} sx={{ mb: 2 }}>
<Box>
{preview ? (
<Avatar
src={preview}
alt="Profile Preview"
sx={{ width: 100, height: 100 }}
/>
) : (
<Avatar sx={{ width: 100, height: 100, bgcolor: 'grey.300' }} />
)}
</Box>
<Box>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept="image/*"
style={{ display: 'none' }}
/>
<Button
variant="outlined"
startIcon={<CloudUploadIcon />}
onClick={triggerFileInput}
>
Choose File
</Button>
</Box>
</Stack>
{/* Action buttons - shown only when an image is selected */}
{preview && (
<Stack direction="row" spacing={2} justifyContent="flex-end">
<Button
variant="outlined"
startIcon={<CancelIcon />}
onClick={handleCancel}
>
Cancel
</Button>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={handleUpload}
>
Upload
</Button>
</Stack>
)}
</Box>
{/* Profile Form */}
<Box component="form" sx={{ mb: 4 }}>
<TextField
label="Your Full Name"
value="sysadmin"
fullWidth
margin="normal"
InputProps={{
readOnly: true,
}}
/>
<TextField
label="Pronouns"
placeholder="Enter Pronouns"
fullWidth
margin="normal"
/>
<TextField
label="Role"
placeholder="Enter Role"
fullWidth
margin="normal"
/>
<TextField
label="Department or Team"
placeholder="Enter Department"
fullWidth
margin="normal"
/>
<TextField
label="Email"
value="sysadmin"
fullWidth
margin="normal"
InputProps={{
readOnly: true,
}}
/>
<TextField
label="About Me"
placeholder="Enter Description"
fullWidth
margin="normal"
multiline
rows={4}
/>
<Button
variant="contained"
sx={{ mt: 2 }}
onClick={() => navigate('/error404')}
>
Update Profile
</Button>
</Box>
<Divider sx={{ my: 3 }} />
<Typography paragraph>
<strong>Password:</strong> Change Password for your account{' '}
<Link to="/admin/resetpassword">Change password</Link>
</Typography>
<Divider sx={{ my: 3 }} />
<Typography paragraph>
<strong>Security:</strong> Logout of all sessions except this current browser{' '}
<Button color="primary" onClick={handleLogout}>
Logout other sessions
</Button>
</Typography>
<Divider sx={{ my: 3 }} />
<Typography paragraph>
<strong>Deactivation:</strong> Remove access to all organizations and workspace in CloudnSure{' '}
<Button color="primary" onClick={handleLogout}>
Deactivate account
</Button>
</Typography>
</Container>
);
};
export default Profile;

View File

@@ -1,62 +1,62 @@
import React, { useState } from 'react';
function App() {
const [formData, setFormData] = useState({
name: '',
test: [
{ t1: '', t2: '' } // Initial test2
]
});
const handleChange = (e, index) => {
const { name, value } = e.target;
if (name === 'name') {
setFormData({ ...formData, [name]: value });
} else {
const updatedTests = [...formData.test];
updatedTests[index][name] = value;
setFormData({ ...formData, test: updatedTests });
}
};
const handleAddTest = () => {
setFormData({
...formData,
test: [...formData.test, { t1: '', t2: '' }]
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<div className="container">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" value={formData.name} onChange={handleChange} required />
</div>
<hr />
<h2>Test 2</h2>
{formData.test.map((test, index) => (
<div key={index}>
<div className="form-group">
<label htmlFor={`t1-${index}`}>Name:</label>
<input type="text" id={`t1-${index}`} name={`t1-${index}`} value={test.t1} onChange={(e) => handleChange(e, index)} required />
</div>
<div className="form-group">
<label htmlFor={`t2-${index}`}>Description:</label>
<input type="text" id={`t2-${index}`} name={`t2-${index}`} value={test.t2} onChange={(e) => handleChange(e, index)} required />
</div>
</div>
))}
<button type="button" onClick={handleAddTest}>Add Test</button>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default App;
import React, { useState } from 'react';
function App() {
const [formData, setFormData] = useState({
name: '',
test: [
{ t1: '', t2: '' } // Initial test2
]
});
const handleChange = (e, index) => {
const { name, value } = e.target;
if (name === 'name') {
setFormData({ ...formData, [name]: value });
} else {
const updatedTests = [...formData.test];
updatedTests[index][name] = value;
setFormData({ ...formData, test: updatedTests });
}
};
const handleAddTest = () => {
setFormData({
...formData,
test: [...formData.test, { t1: '', t2: '' }]
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<div className="container">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" value={formData.name} onChange={handleChange} required />
</div>
<hr />
<h2>Test 2</h2>
{formData.test.map((test, index) => (
<div key={index}>
<div className="form-group">
<label htmlFor={`t1-${index}`}>Name:</label>
<input type="text" id={`t1-${index}`} name={`t1-${index}`} value={test.t1} onChange={(e) => handleChange(e, index)} required />
</div>
<div className="form-group">
<label htmlFor={`t2-${index}`}>Description:</label>
<input type="text" id={`t2-${index}`} name={`t2-${index}`} value={test.t2} onChange={(e) => handleChange(e, index)} required />
</div>
</div>
))}
<button type="button" onClick={handleAddTest}>Add Test</button>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default App;

View File

@@ -1,73 +1,73 @@
import React, { useState } from 'react';
function MyForm() {
const [formData, setFormData] = useState({
name: '',
test2: { name: '', description: '' }
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
const handleTest2Change = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
test2: {
...formData.test2,
[name]: value
}
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
// we can send this data to our server
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
</div>
<hr />
<h2>Test 2</h2>
<div>
<label htmlFor="test2Name">Name:</label>
<input
type="text"
id="test2Name"
name="name"
value={formData.test2.name}
onChange={handleTest2Change}
/>
</div>
<div>
<label htmlFor="test2Description">Description:</label>
<input
type="text"
id="test2Description"
name="description"
value={formData.test2.description}
onChange={handleTest2Change}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;
import React, { useState } from 'react';
function MyForm() {
const [formData, setFormData] = useState({
name: '',
test2: { name: '', description: '' }
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
const handleTest2Change = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
test2: {
...formData.test2,
[name]: value
}
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
// we can send this data to our server
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
</div>
<hr />
<h2>Test 2</h2>
<div>
<label htmlFor="test2Name">Name:</label>
<input
type="text"
id="test2Name"
name="name"
value={formData.test2.name}
onChange={handleTest2Change}
/>
</div>
<div>
<label htmlFor="test2Description">Description:</label>
<input
type="text"
id="test2Description"
name="description"
value={formData.test2.description}
onChange={handleTest2Change}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;

View File

@@ -1,71 +1,71 @@
/* Report.css */
.app {
font-family: Arial, sans-serif;
margin: 20px auto; /* Adjust margin to create space between cards and navbar */
padding: 0 20px;
max-width: 800px;
}
h1 {
text-align: center;
color: #333;
}
.card-container {
display: grid;
grid-template-columns: minmax(350px, 1fr) minmax(350px, 1fr) minmax(350px, 1fr) repeat(auto-fill, minmax(250px, 1fr)); /* Ensure first three cards are in the same row and increase their width */
gap: 20px;
max-width: 100%; /* Limit the maximum width of the card container */
margin: 0 auto; /* Center the card container */
}
.card {
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 10px;
padding: 30px; /* Increase padding for better spacing */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Adjust box shadow for a more pronounced effect */
transition: transform 0.3s ease-in-out;
width: 100%; /* Make cards take up full width of their container */
height: auto; /* Allow cards to expand vertically based on content */
}
.card:hover {
transform: translateY(-5px);
}
.card h2 {
margin-top: 0;
color: #333;
font-size: 20px; /* Increase font size of card headings */
}
.card p {
margin: 10px 0; /* Increase margin for better spacing */
color: #666;
}
@media (max-width: 1400px) {
.card-container {
grid-template-columns: minmax(300px, 1fr) minmax(300px, 1fr) repeat(auto-fill, minmax(250px, 1fr)); /* Adjust columns for smaller screens */
}
}
@media (max-width: 1100px) {
.card-container {
grid-template-columns: minmax(250px, 1fr) repeat(auto-fill, minmax(200px, 1fr)); /* Further adjust columns for even smaller screens */
}
}
@media (max-width: 900px) {
.card-container {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Adjust columns for smaller screens */
}
}
@media (max-width: 600px) {
.card-container {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Adjust columns for smaller screens */
}
}
/* Report.css */
.app {
font-family: Arial, sans-serif;
margin: 20px auto; /* Adjust margin to create space between cards and navbar */
padding: 0 20px;
max-width: 800px;
}
h1 {
text-align: center;
color: #333;
}
.card-container {
display: grid;
grid-template-columns: minmax(350px, 1fr) minmax(350px, 1fr) minmax(350px, 1fr) repeat(auto-fill, minmax(250px, 1fr)); /* Ensure first three cards are in the same row and increase their width */
gap: 20px;
max-width: 100%; /* Limit the maximum width of the card container */
margin: 0 auto; /* Center the card container */
}
.card {
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 10px;
padding: 30px; /* Increase padding for better spacing */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Adjust box shadow for a more pronounced effect */
transition: transform 0.3s ease-in-out;
width: 100%; /* Make cards take up full width of their container */
height: auto; /* Allow cards to expand vertically based on content */
}
.card:hover {
transform: translateY(-5px);
}
.card h2 {
margin-top: 0;
color: #333;
font-size: 20px; /* Increase font size of card headings */
}
.card p {
margin: 10px 0; /* Increase margin for better spacing */
color: #666;
}
@media (max-width: 1400px) {
.card-container {
grid-template-columns: minmax(300px, 1fr) minmax(300px, 1fr) repeat(auto-fill, minmax(250px, 1fr)); /* Adjust columns for smaller screens */
}
}
@media (max-width: 1100px) {
.card-container {
grid-template-columns: minmax(250px, 1fr) repeat(auto-fill, minmax(200px, 1fr)); /* Further adjust columns for even smaller screens */
}
}
@media (max-width: 900px) {
.card-container {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Adjust columns for smaller screens */
}
}
@media (max-width: 600px) {
.card-container {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Adjust columns for smaller screens */
}
}

View File

@@ -1,80 +1,74 @@
import React, { useState, useEffect } from "react";
const Card = ({ report }) => {
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
<h2 className="text-lg font-semibold mb-2">{report.reportName}</h2>
<p className="text-gray-600 mb-2">{report.description}</p>
<p className="text-gray-600 mb-2">
Active: {report.active ? "Yes" : "No"}
</p>
<p className="text-gray-600">Is SQL: {report.isSql ? "Yes" : "No"}</p>
</div>
);
};
// Define the API base URL using the environment variable
const api = process.env.REACT_APP_API_BASE_URL;
const Report = () => {
const [reports, setReports] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("authToken");
if (!token) {
console.error("No auth token found. Redirecting to login.");
// You can redirect to the login page here if needed
return;
}
try {
const response = await fetch(`${api}/Rpt_builder2/Rpt_builder2`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.status === 401) {
console.error("Unauthorized. Redirecting to login.");
// Redirect to the login page here if needed
return;
}
if (!response.ok) {
throw new Error("Failed to fetch data");
}
const data = await response.json();
setReports(data);
setLoading(false);
} catch (error) {
console.error("Error fetching data:", error);
setLoading(false);
}
};
fetchData();
}, []);
return (
<div className="min-h-screen bg-gray-100 p-4 flex flex-col items-center">
<div className="mb-6">
<h1 className="text-3xl font-semibold text-gray-700">Reports</h1>
</div>
{loading ? (
<p className="text-gray-700">Loading...</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{reports.map((report) => (
<Card key={report.id} report={report} />
))}
</div>
)}
</div>
);
};
export default Report;
import React, { useState, useEffect } from "react";
const Card = ({ report }) => {
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
<h2 className="text-lg font-semibold mb-2">{report.reportName}</h2>
<p className="text-gray-600 mb-2">{report.description}</p>
<p className="text-gray-600 mb-2">
Active: {report.active ? "Yes" : "No"}
</p>
<p className="text-gray-600">Is SQL: {report.isSql ? "Yes" : "No"}</p>
</div>
);
};
// Define the API base URL using the environment variable
const api = process.env.REACT_APP_API_BASE_URL;
const Report = () => {
const [reports, setReports] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem("authToken");
if (!token) {
console.error("No auth token found. Redirecting to login.");
// You can redirect to the login page here if needed
return;
}
try {
const response = await fetch(`${api}/Rpt_builder2/Rpt_builder2`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error("Failed to fetch data");
}
const data = await response.json();
setReports(data);
setLoading(false);
} catch (error) {
console.error("Error fetching data:", error);
setLoading(false);
}
};
fetchData();
}, []);
return (
<div className="min-h-screen bg-gray-100 p-4 flex flex-col items-center">
<div className="mb-6">
<h1 className="text-3xl font-semibold text-gray-700">Reports</h1>
</div>
{loading ? (
<p className="text-gray-700">Loading...</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{reports.map((report) => (
<Card key={report.id} report={report} />
))}
</div>
)}
</div>
);
};
export default Report;

View File

@@ -0,0 +1,701 @@
import React, { useState, useEffect } from "react";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
TextField,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
Select,
IconButton,
Typography,
Box,
CircularProgress,
Pagination,
Chip,
FormControlLabel,
Checkbox,
InputAdornment,
Tooltip,
Divider
} from "@mui/material";
import {
Add as AddIcon,
Edit as EditIcon,
Delete as DeleteIcon,
Settings as SettingsIcon,
ArrowBack as ArrowBackIcon,
Search as SearchIcon,
Close as CloseIcon,
PlayArrow as PlayArrowIcon,
ViewColumn as ViewColumnIcon,
List as ListIcon,
MoreVert as MoreVertIcon
} from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import ReportSqlBuilderApi from "APIServices/ReportSQLBuilderAPI";
import { addSQLReport } from "APIServices/AddReportSQLBuilderAPI";
import { UpdateReportSQLBuilder } from "APIServices/UpdateReportSQLBuilderAPI";
import { deleteReportSQL } from "APIServices/DeleteReportSQLAPI";
import ConfirmModal from "../../common/ConfirmModal";
import ReportBuilderService from "../../../APIServices/ReportBuilderService";
function UserDetailsView() {
const [userDetails, setUserDetails] = useState([]);
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(true);
const [recordsPerPage, setRecordsPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const navigate = useNavigate();
const [showModal, setShowModal] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [selectedReport, setSelectedReport] = useState(null);
const [editData, setEditData] = useState({
reportName: "",
description: "",
active: false,
});
const [newData, setNewData] = useState({
reportName: "",
description: "",
active: false,
isSql: true,
Rpt_builder2_lines: [{ model: "" }],
});
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [deleteItem, setDeleteItem] = useState({ index: null, id: null });
const [showReportConfigModal, setShowReportConfigModal] = useState(false);
const [reportConfigData, setReportConfigData] = useState({
conn_name: "",
date_param_req: "",
std_param_html: "",
adhoc_param_html: "",
column_str: "",
sql_str: "",
id: null
});
const [databaseConnections, setDatabaseConnections] = useState([]);
const fetchUserDetails = async () => {
try {
setLoading(true);
const data = await ReportSqlBuilderApi.fetchUserDetails();
setUserDetails(data);
toast.success('Data fetched successfully!');
} catch (error) {
console.error('Error fetching user details:', error);
toast.error('Failed to fetch user details.');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUserDetails();
}, []);
const handlePlayClick = () => {
if (selectedReport) {
goToRunner(selectedReport);
} else {
toast.error("No report selected to run.");
}
};
const handleRecordsPerPageChange = (event) => {
setRecordsPerPage(parseInt(event.target.value, 10));
setCurrentPage(1);
};
const handleInputChange = (event) => {
const { name, value, checked, type } = event.target;
const inputValue = type === "checkbox" ? checked : value;
if (showModal) {
setEditData((prev) => ({ ...prev, [name]: inputValue }));
} else if (showAddModal) {
setNewData((prev) => ({ ...prev, [name]: inputValue }));
}
};
const handleSubmit = async () => {
try {
const updatedReport = await UpdateReportSQLBuilder(editData.id, editData);
toast.success("Report Updated successfully..");
const updatedDetails = userDetails.map((detail) =>
detail.id === editData.id ? { ...detail, ...editData } : detail
);
setUserDetails(updatedDetails);
setShowModal(false);
} catch (error) {
console.error("Error updating the report: ", error);
toast.error("Failed to update report");
}
};
const filteredUsers = userDetails.filter(
(item) =>
item.reportName &&
item.reportName.toLowerCase().includes(searchQuery.toLowerCase())
);
const slicedUsers = filteredUsers.slice(
(currentPage - 1) * recordsPerPage,
currentPage * recordsPerPage
);
const totalPages = Math.ceil(filteredUsers.length / recordsPerPage);
const handlePageChange = (event, page) => {
setCurrentPage(page);
};
const handleGoTo = (data) => {
const hasReportData = data.rpt_builder2_lines?.some(line => line.model && line.model.trim() !== "");
if (hasReportData) {
fetchReportConfiguration(data.id, data);
} else {
navigate("/admin/reportquery", { state: { id: data.id, reportData: data } });
}
};
const handleRowClick = (report) => {
fetchReportConfiguration(report.id, report);
};
const fetchDatabaseConnections = async () => {
try {
setLoading(true);
const response = await ReportBuilderService.getDatabase();
if (response && response.data) {
setDatabaseConnections(response.data);
} else {
setDatabaseConnections([]);
}
} catch (error) {
console.error("Error fetching database connections: ", error);
toast.error("Failed to load database connections");
setDatabaseConnections([]);
} finally {
setLoading(false);
}
};
const fetchReportConfiguration = async (reportId, report) => {
try {
setLoading(true);
const response = await ReportBuilderService.getrbLineDetailsById(report.rpt_builder2_lines[0].id);
await fetchDatabaseConnections();
if (response && response.data) {
let configData = {};
try {
if (report.Rpt_builder2_lines && report.Rpt_builder2_lines.length > 0) {
const modelData = JSON.parse(report.Rpt_builder2_lines[0].model);
configData = {
conn_name: modelData.conn_name || "",
date_param_req: modelData.date_param_req || "",
std_param_html: modelData.std_param_html || "",
adhoc_param_html: modelData.adhoc_param_html || "",
column_str: modelData.column_str || "",
sql_str: modelData.sql_str || "",
id: report.rpt_builder2_lines[0].id
};
} else {
configData = {
conn_name: "",
date_param_req: "",
std_param_html: "",
adhoc_param_html: "",
column_str: "",
sql_str: "",
id: report.rpt_builder2_lines[0].id
};
}
} catch (error) {
console.error("Error parsing report configuration: ", error);
toast.error("Failed to parse report configuration");
configData = {
conn_name: "",
date_param_req: "",
std_param_html: "",
adhoc_param_html: "",
column_str: "",
sql_str: "",
id: report.rpt_builder2_lines[0].id
};
}
setReportConfigData(configData);
setShowReportConfigModal(true);
} else {
toast.error("No configuration data found for this report");
}
} catch (error) {
console.error("Error fetching report configuration: ", error);
toast.error("Failed to load report configuration");
} finally {
setLoading(false);
}
};
const handleReportConfigClose = () => {
setShowReportConfigModal(false);
};
const handleReportConfigChange = (e) => {
const { name, value } = e.target;
setReportConfigData(prev => ({
...prev,
[name]: value
}));
};
const handleReportConfigUpdate = async () => {
try {
setLoading(true);
const reportData = {
conn_name: reportConfigData.conn_name,
sql_str: reportConfigData.sql_str,
date_param_req: reportConfigData.date_param_req,
column_str: reportConfigData.column_str,
adhoc_param_html: reportConfigData.adhoc_param_html,
std_param_html: reportConfigData.std_param_html,
};
const saveReportData = {
header_id: reportConfigData.id,
model: JSON.stringify(reportData),
};
const response = await ReportBuilderService.updaterbLineData(saveReportData, reportConfigData.id);
toast.success("Report configuration updated successfully");
setShowReportConfigModal(false);
fetchUserDetails();
} catch (error) {
console.error("Error updating report configuration: ", error);
toast.error("Failed to update report configuration");
} finally {
setLoading(false);
}
};
const handleDelete = async () => {
const { id } = deleteItem;
if (!id) {
toast.error("Invalid ID. Deletion failed.");
return;
}
try {
const deletedReport = await deleteReportSQL(id);
toast.success("Report deleted successfully");
setUserDetails(userDetails.filter(item => item.id !== id));
} catch (error) {
console.error("Error in deleting report:", error);
toast.error("Failed to delete report");
}
};
const confirmDelete = (e, index, id, reportName) => {
e.stopPropagation();
setDeleteItem({ index, id, reportName });
setShowConfirmModal(true);
};
const handleEdit = (detail) => {
setEditData(detail);
setShowModal(true);
};
const handleClose = () => {
setShowModal(false);
};
const handleAddClose = () => {
setShowAddModal(false);
};
const handleAddSubmit = async () => {
try {
const addedReport = await addSQLReport(newData);
setUserDetails((prevUserDetails) => [...prevUserDetails, addedReport]);
setNewData({
reportName: "",
description: "",
active: false,
isSql: true
});
setShowAddModal(false);
toast.success("User report added successfully!");
} catch (error) {
toast.error("Failed to add the user report. Please try again.");
console.error("Error adding user report: ", error);
}
};
const goToRunner = (report) => {
if (!report || typeof report.isSql === "undefined") {
console.error("Invalid report object:", report);
return;
}
if (report.isSql) {
navigate(`/admin/report-runner/${report.id}`);
} else {
navigate(`/admin/report-runner2/${report.id}`);
}
};
return (
<Box sx={{ p: 4 }}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 4
}}>
<Typography variant="h4" component="h2">
Report Builder (SQL)
</Typography>
<Box sx={{ display: 'flex', gap: 2 }}>
<Button
variant="contained"
startIcon={<PlayArrowIcon />}
onClick={() => navigate('/admin/report-runner')}
>
Report Runner
</Button>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setShowAddModal(true)}
>
Add
</Button>
</Box>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
<TextField
placeholder="Search"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
endAdornment: searchQuery && (
<InputAdornment position="end">
<IconButton onClick={() => setSearchQuery("")}>
<CloseIcon fontSize="small" />
</IconButton>
</InputAdornment>
),
}}
sx={{ width: '50%' }}
/>
</Box>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<CircularProgress />
</Box>
) : (
<>
<TableContainer component={Paper} sx={{ mb: 3 }}>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: 'primary.main' }}>
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Go To</TableCell>
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Report Name</TableCell>
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Description</TableCell>
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Status</TableCell>
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{slicedUsers.length === 0 ? (
<TableRow>
<TableCell colSpan={5} align="center">
No records found.
</TableCell>
</TableRow>
) : (
slicedUsers.filter((detail) => detail.isSql).map((detail, index) => (
<TableRow
key={index}
hover
onClick={() => handleRowClick(detail)}
sx={{ cursor: 'pointer' }}
>
<TableCell>
<IconButton
onClick={(e) => {
e.stopPropagation();
handleGoTo(detail);
}}
>
<SettingsIcon color="primary" />
</IconButton>
</TableCell>
<TableCell>{detail.reportName}</TableCell>
<TableCell>{detail.description || "No description available"}</TableCell>
<TableCell>
<Chip
label={detail.active ? "Active" : "Inactive"}
color={detail.active ? "success" : "error"}
size="small"
/>
</TableCell>
<TableCell>
<IconButton
onClick={(e) => {
e.stopPropagation();
handleEdit(detail);
}}
>
<EditIcon color="primary" />
</IconButton>
<IconButton
onClick={(e) => {
e.stopPropagation();
confirmDelete(e, index, detail.id, detail.reportName);
}}
>
<DeleteIcon color="error" />
</IconButton>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography variant="body2">
Rows per page:
</Typography>
<Select
value={recordsPerPage}
onChange={handleRecordsPerPageChange}
size="small"
sx={{ height: '36px' }}
>
{[5, 10, 25, 50].map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</Box>
<Pagination
count={totalPages}
page={currentPage}
onChange={handlePageChange}
color="primary"
showFirstButton
showLastButton
/>
</Box>
</>
)}
{/* Edit Dialog */}
<Dialog open={showModal} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle>Edit User Detail</DialogTitle>
<DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
<TextField
label="Report Name"
name="reportName"
value={editData.reportName || ""}
onChange={handleInputChange}
fullWidth
/>
<TextField
label="Description"
name="description"
value={editData.description || ""}
onChange={handleInputChange}
fullWidth
multiline
rows={3}
/>
<FormControlLabel
control={
<Checkbox
name="active"
checked={editData.active || false}
onChange={handleInputChange}
/>
}
label="Active"
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleSubmit} variant="contained">Save Changes</Button>
</DialogActions>
</Dialog>
{/* Add Dialog */}
<Dialog open={showAddModal} onClose={handleAddClose} maxWidth="sm" fullWidth>
<DialogTitle>Add New User Detail</DialogTitle>
<DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
<TextField
label="Report Name"
name="reportName"
value={newData.reportName}
onChange={handleInputChange}
fullWidth
required
/>
<TextField
label="Description"
name="description"
value={newData.description}
onChange={handleInputChange}
fullWidth
multiline
rows={3}
required
/>
<FormControlLabel
control={
<Checkbox
name="active"
checked={newData.active}
onChange={handleInputChange}
/>
}
label="Active"
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleAddClose}>Cancel</Button>
<Button onClick={handleAddSubmit} variant="contained">Add User Detail</Button>
</DialogActions>
</Dialog>
{/* Confirmation Dialog */}
<ConfirmModal
open={showConfirmModal}
onClose={() => setShowConfirmModal(false)}
onConfirm={handleDelete}
title="Confirm Deletion"
message={`Are you sure you want to delete "${deleteItem.reportName || 'this report'}"? This action cannot be undone.`}
confirmText="Delete"
cancelText="Cancel"
/>
{/* Report Configuration Dialog */}
<Dialog open={showReportConfigModal} onClose={handleReportConfigClose} maxWidth="md" fullWidth>
<DialogTitle>Report Configuration</DialogTitle>
<DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 2 }}>
<Box sx={{ display: 'flex', gap: 2 }}>
<Select
label="Connection Name"
name="conn_name"
value={reportConfigData.conn_name}
onChange={handleReportConfigChange}
fullWidth
sx={{ mb: 2 }}
>
<MenuItem value="">Select Database Connection</MenuItem>
{databaseConnections.map((db, index) => (
<MenuItem key={index} value={db.name}>
{db.name}
</MenuItem>
))}
</Select>
<Select
label="Date Parameter Required"
name="date_param_req"
value={reportConfigData.date_param_req}
onChange={handleReportConfigChange}
fullWidth
sx={{ mb: 2 }}
>
<MenuItem value="">Select Option</MenuItem>
<MenuItem value="Yes">Yes</MenuItem>
<MenuItem value="No">No</MenuItem>
</Select>
</Box>
<TextField
label="SQL String"
name="sql_str"
value={reportConfigData.sql_str}
onChange={handleReportConfigChange}
fullWidth
multiline
rows={5}
sx={{ fontFamily: 'monospace' }}
/>
<TextField
label="Column String (html)"
name="column_str"
value={reportConfigData.column_str}
onChange={handleReportConfigChange}
fullWidth
multiline
rows={3}
/>
<Box sx={{ display: 'flex', gap: 2 }}>
<TextField
label="Standard Parameter String (html)"
name="std_param_html"
value={reportConfigData.std_param_html}
onChange={handleReportConfigChange}
fullWidth
multiline
rows={3}
/>
<TextField
label="Adhoc Parameter String (html)"
name="adhoc_param_html"
value={reportConfigData.adhoc_param_html}
onChange={handleReportConfigChange}
fullWidth
multiline
rows={3}
/>
</Box>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleReportConfigClose}>Cancel</Button>
<Button
onClick={handleReportConfigUpdate}
variant="contained"
disabled={loading}
>
{loading ? <CircularProgress size={24} /> : 'Update Configuration'}
</Button>
</DialogActions>
</Dialog>
</Box>
);
}
export default UserDetailsView;

View File

@@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { fetchAllReportsApi } from '../../../APIServices/ReportRunnerAPI';
const ReportRunnerAll = () => {
const [gridData, setGridData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const [rowSelected, setRowSelected] = useState(null);
const [modalDelete, setModalDelete] = useState(false);
const [reports, setReports] = useState([]);
const navigate = useNavigate();
useEffect(() => {
fetchAllReports();
}, []);
const fetchAllReports = async () => {
setIsLoading(true);
try {
const data = await fetchAllReportsApi();
console.log("Fetched all reports:", data);
setGridData(data);
setReports(data);
if (data.length === 0) {
setError("No data available");
}
} catch (error) {
console.error("Error while fetching reports:", error);
setError("Error fetching data.");
} finally {
setIsLoading(false);
}
};
const goToAdd = () => navigate("/admin/user-report");
const goToAdd2 = () => navigate("/admin/reportbuild2all");
const goToRunner = (report) => {
console.log("at time of navigating reportID: ",report.id, " report: ",report);
if (report.isSql) {
navigate(`/admin/report-runner1/${report.id}`,{ state: { reportId: report.id, reportData: report}});
} else {
navigate(`/admin/report-runner2/${report.id}`,{ state: { reportId: report.id, reportData: report}});
}
};
const handleDelete = async (id) => {
setModalDelete(false);
try {
const response = await fetch(`/api/report-builder/${id}`, { method: "DELETE" });
if (response.ok) {
toast.success("Deleted successfully");
fetchAllReports();
} else {
toast.error("Error deleting data.");
}
} catch (err) {
console.error(err);
toast.error("Error deleting data.");
}
};
const openDeleteModal = (row) => {
setRowSelected(row);
setModalDelete(true);
};
return (
<div className="container-fluid p-4">
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-4">
<h3 className="m-0"><strong>All Reports</strong></h3>
<div>
<button className="btn btn-primary me-2" onClick={goToAdd}>
<i className="bi bi-plus me-1"></i> Report Builder SQL
</button>
<button className="btn btn-primary" onClick={goToAdd2}>
<i className="bi bi-plus me-1"></i> Report Builder URL
</button>
</div>
</div>
{/* Loading Spinner */}
{isLoading && (
<div className="alert alert-info d-flex align-items-center mt-3">
<div className="spinner-border me-2" role="status"></div>
Loading...
</div>
)}
{/* Report Cards */}
{!isLoading && gridData.length > 0 && (
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
{gridData.map((report, index) => (
<div className="col" key={index}>
<div
className="card h-100 shadow-sm border-0"
onClick={() => goToRunner(report)}
style={{ cursor: 'pointer' }}
>
{/* Card Header */}
<div
className={`card-header d-flex justify-content-between align-items-center ${report.isSql ? 'bg-primary bg-opacity-10' : 'bg-warning bg-opacity-10'}`}
>
<div className="d-flex align-items-center">
<div className={`me-2 p-2 rounded ${report.isSql ? 'bg-primary bg-opacity-25' : 'bg-warning bg-opacity-25'}`}>
<i className={`bi ${report.isSql ? 'bi-database' : 'bi-link-45deg'} ${report.isSql ? 'text-primary' : 'text-warning'}`}></i>
</div>
<span className={`fw-semibold ${report.isSql ? 'text-primary' : 'text-warning'}`}>
{report.isSql == null ? "N/A" : report.isSql ? "SQL Report" : "URL Report"}
</span>
</div>
<span className={`badge ${report.active ? 'bg-success' : 'bg-danger'}`}>
{report.active ? "Active" : "Inactive"}
</span>
</div>
{/* Card Body */}
<div className="card-body">
<h5 className="card-title fw-bold text-truncate" title={report.reportName}>
{report.reportName}
</h5>
<p
className="card-text text-muted"
style={{
display: '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
height: '42px'
}}
title={report.description}
>
{report.description || "No description available"}
</p>
</div>
{/* Card Footer */}
<div className="card-footer bg-light">
<div className="d-flex justify-content-between align-items-center">
<small className="text-muted">
<i className="bi bi-clock me-1"></i>
Updated: {new Date(report.updatedAt).toLocaleDateString()}
</small>
<button
className="btn btn-sm btn-outline-secondary"
onClick={(e) => {
e.stopPropagation();
openDeleteModal(report);
}}
>
<i className="bi bi-three-dots"></i>
</button>
</div>
</div>
</div>
</div>
))}
</div>
)}
{/* Error */}
{error && <div className="alert alert-danger mt-3">{error}</div>}
{/* Delete Modal */}
{modalDelete && (
<div className="modal show d-block" tabIndex="-1" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Delete Confirmation</h5>
<button
type="button"
className="btn-close"
onClick={() => setModalDelete(false)}
></button>
</div>
<div className="modal-body">
<p>Are you sure you want to delete the report?</p>
<h6>{rowSelected?.reportName}</h6>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={() => setModalDelete(false)}
>
Cancel
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => handleDelete(rowSelected.id)}
>
Delete
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default ReportRunnerAll;

View File

@@ -1,100 +1,118 @@
/* sidebar.css */
/* Common styles */
.sidebar {
width: 250px; /* Initial width */
height: 100%;
background-color: #778184;
color: #0e0e0e;
overflow-y: auto;
transition: width 0.3s;
}
.sidebar.collapsed {
width: 60px; /* Collapsed width */
}
.sidebar .navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background-color: #5f6265;
}
.sidebar .navbar button {
background: none;
border: none;
font-size: 1.5em;
color: #fff;
cursor: pointer;
}
.sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar li {
padding: 10px;
cursor: pointer;
transition: background-color 0.3s;
}
.sidebar li:hover {
background-color: #555;
}
/* Responsive styles */
@media (max-width: 768px) {
.sidebar {
width: 60px; /* Collapsed width for smaller screens */
}
.sidebar.collapsed {
width: 60px; /* Ensure sidebar stays collapsed on smaller screens */
}
.sidebar .navbar h2 {
display: none; /* Hide the sidebar title on smaller screens */
}
.sidebar .navbar button {
font-size: 1.2em; /* Reduce button size on smaller screens */
}
.sidebar li {
padding: 8px; /* Reduce padding for menu items on smaller screens */
}
.sidebar ul {
padding-left: 0; /* Remove left padding for nested UL on smaller screens */
}
.sidebar li div {
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar li div svg {
display: none; /* Hide submenu toggle icons on smaller screens */
}
.sidebar li div .submenu-icon {
display: inline-block; /* Show submenu toggle icon as inline-block */
}
.sidebar li div .submenu-icon svg {
margin-left: 5px; /* Add margin to submenu toggle icon */
}
.sidebar ul ul {
display: none; /* Hide submenus by default on smaller screens */
}
.sidebar ul ul.active {
display: block; /* Show active submenus on smaller screens */
}
}
/* sidebar.css */
/* Common styles */
.sidebar {
width: 250px; /* Initial width */
height: 100%;
background-color: #778184;
color: #0e0e0e;
overflow-y: auto;
transition: width 0.3s;
}
.sidebar.collapsed {
width: 60px; /* Collapsed width */
}
.sidebar .navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background-color: #5f6265;
}
.sidebar .navbar button {
background: none;
border: none;
font-size: 1.5em;
color: #fff;
cursor: pointer;
}
.sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar li {
padding: 10px;
cursor: pointer;
transition: background-color 0.3s;
}
.sidebar li:hover {
background-color: #555;
}
/* For the main content adjustment */
.main-content {
margin-left: 220px;
transition: margin-left 0.3s ease;
}
.main-content.sidebar-collapsed {
margin-left: 60px;
}
/* Active menu item style */
.Mui-selected {
background-color: #5e72e4 !important;
color: white !important;
}
.Mui-selected:hover {
background-color: #233dd2 !important;
}
/* Responsive styles */
@media (max-width: 768px) {
.sidebar {
width: 60px; /* Collapsed width for smaller screens */
}
.sidebar.collapsed {
width: 60px; /* Ensure sidebar stays collapsed on smaller screens */
}
.sidebar .navbar h2 {
display: none; /* Hide the sidebar title on smaller screens */
}
.sidebar .navbar button {
font-size: 1.2em; /* Reduce button size on smaller screens */
}
.sidebar li {
padding: 8px; /* Reduce padding for menu items on smaller screens */
}
.sidebar ul {
padding-left: 0; /* Remove left padding for nested UL on smaller screens */
}
.sidebar li div {
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar li div svg {
display: none; /* Hide submenu toggle icons on smaller screens */
}
.sidebar li div .submenu-icon {
display: inline-block; /* Show submenu toggle icon as inline-block */
}
.sidebar li div .submenu-icon svg {
margin-left: 5px; /* Add margin to submenu toggle icon */
}
.sidebar ul ul {
display: none; /* Hide submenus by default on smaller screens */
}
.sidebar ul ul.active {
display: block; /* Show active submenus on smaller screens */
}
}

View File

@@ -1,119 +1,193 @@
import React, { useEffect, useState } from "react";
import { FiChevronRight, FiChevronDown, FiSettings } from "react-icons/fi";
import { Link } from "react-router-dom";
import { FaChevronRight as FaChevronRightIcon } from "react-icons/fa";
const Sidebar = ({ menus, handleMenuItemClick, collapsed }) => {
const [isOpen, setIsOpen] = useState(true);
const [openSubmenu, setOpenSubmenu] = useState(null);
const [menuItems, setMenuItems] = useState([]);
useEffect(() => {
const fetchMenuItems = async () => {
const token = localStorage.getItem("authToken");
try {
const res = await fetch(
`${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await res.json();
setMenuItems(data);
console.log("Fetched Menu Data:", data);
} catch (error) {
console.error("Error fetching menu items:", error);
}
};
fetchMenuItems();
}, []);
const handleToggle = () => setIsOpen(!isOpen);
const handleSubmenuToggle = (menu) => {
setOpenSubmenu(openSubmenu === menu ? null : menu);
};
return (
<div
className={`flex flex-col h-screen bg-gray-100 text-black ${isOpen ? "w-64" : "w-20"
} transition-all duration-300`}
>
<div className="flex items-center justify-between p-4">
{isOpen ? (
<span className="text-lg font-bold">Menu</span>
) : (
<FiSettings
className="text-2xl cursor-pointer"
onClick={handleToggle}
/>
)}
<button onClick={handleToggle}>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16m-7 6h7"
></path>
</svg>
</button>
</div>
{isOpen && (
<nav className="flex flex-col mt-4">
{menuItems.map((item) => (
<div key={item.id} className="relative">
<button
className="flex items-center p-4 hover:bg-gray-300 w-full text-left"
onClick={() => handleSubmenuToggle(item.id)}
>
{item.icon && <item.icon className="w-6 h-6 mr-2" />}
<span>{item.menuItemDesc}</span>
{!collapsed && (
<FaChevronRightIcon className="w-4 h-4 opacity-0 group-hover:opacity-100 transform group-hover:translate-x-1 transition-all duration-200" />
)}
</button>
{/*
<Link to={`${subItem.menuItemDesc}`} ><span className="ml-4">{subItem.menuItemDesc}</span> </Link>
*/}
{item.subMenus && openSubmenu === item.id && (
<ul className="pl-8 bg-gray-200">
{item.subMenus.map((subItem) => (
<Link to={`/${subItem.menuItemDesc}`} style={{ textDecoration: 'none', }}>
<li
key={subItem.id}
className="flex items-center p-2 hover:bg-gray-300"
>
<span className="ml-4">{subItem.menuItemDesc}</span>
{subItem.subMenus && (
<div className="ml-auto">
{openSubmenu === subItem.id ? (
<FiChevronDown />
) : (
<FiChevronRight />
)}
</div>
)}
</li>
</Link>
))}
</ul>
)}
</div>
))}
</nav>
)}
</div>
);
};
export default Sidebar;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
Divider,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
styled,
Collapse,
useTheme
} from '@mui/material';
import {
ChevronLeft,
ChevronRight,
ExpandMore,
ExpandLess,
Description as DescriptionIcon,
Error as ErrorIcon,
Storage as StorageIcon,
SwapHoriz as SwapHorizIcon
} from '@mui/icons-material';
const drawerWidth = 220;
const collapsedWidth = 60;
const Sidebar = ({ onSidebarToggle }) => {
const theme = useTheme();
const [collapsed, setCollapsed] = useState(true);
const [openTransaction, setOpenTransaction] = useState(false);
const navigate = useNavigate();
const handleToggle = () => {
setCollapsed(!collapsed);
if (onSidebarToggle) {
onSidebarToggle(!collapsed);
}
};
const handleTransactionToggle = () => {
setOpenTransaction(!openTransaction);
};
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
}));
return (
<>
<Drawer
variant="permanent"
sx={{
width: collapsed ? collapsedWidth : drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: collapsed ? collapsedWidth : drawerWidth,
boxSizing: 'border-box',
backgroundColor: theme.palette.grey[200], // Consistent with theme
borderRight: '1px solid rgba(0, 0, 0, 0.12)',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
height: '100vh',
position: 'relative'
},
}}
>
<DrawerHeader>
<IconButton onClick={handleToggle} size="small">
{collapsed ? <ChevronRight /> : <ChevronLeft />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{/* Transaction with submenus */}
<ListItem disablePadding>
<ListItemButton
onClick={handleTransactionToggle}
sx={{
minHeight: 48,
justifyContent: collapsed ? 'center' : 'initial',
px: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: collapsed ? 'auto' : 3,
justifyContent: 'center',
color: 'inherit'
}}
>
<SwapHorizIcon />
</ListItemIcon>
{!collapsed && (
<>
<ListItemText primary="Transaction" />
{openTransaction ? <ExpandLess /> : <ExpandMore />}
</>
)}
</ListItemButton>
</ListItem>
{!collapsed && (
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
<ListItemButton
sx={{ pl: 4 }}
onClick={() => navigate('/admin/regform')}
>
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
<DescriptionIcon />
</ListItemIcon>
<ListItemText primary="Regform" />
</ListItemButton>
<ListItemButton
sx={{ pl: 4 }}
onClick={() => navigate('/admin/error404')}
>
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
<ErrorIcon />
</ListItemIcon>
<ListItemText primary="Additional container" />
</ListItemButton>
</List>
</Collapse>
)}
{/* Masters (single item) */}
<ListItem disablePadding>
<ListItemButton
onClick={() => navigate('/admin/masters')}
sx={{
minHeight: 48,
justifyContent: collapsed ? 'center' : 'initial',
px: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: collapsed ? 'auto' : 3,
justifyContent: 'center',
color: 'inherit'
}}
>
<StorageIcon />
</ListItemIcon>
{!collapsed && (
<ListItemText primary="Masters" />
)}
</ListItemButton>
</ListItem>
</List>
</Drawer>
{/* Main content spacer */}
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: `calc(100% - ${collapsed ? collapsedWidth : drawerWidth}px)`,
marginLeft: `${collapsed ? collapsedWidth : drawerWidth}px`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
}}
>
{/* Your main content goes here */}
</Box>
</>
);
};
export default Sidebar;

View File

@@ -0,0 +1,308 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import {
getSubmenuItems,
addSubmenuItem,
updateSubmenuItem,
deleteSubmenuItem
} from '../../../ApiServices/menumaintananceapi';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
TextField,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
Select,
IconButton,
Typography,
Box,
CircularProgress
} from "@mui/material";
import {
Add as AddIcon,
Edit as EditIcon,
Delete as DeleteIcon,
MoreVert as MoreVertIcon
} from "@mui/icons-material";
import { toast } from "react-toastify";
const SubMenuMaintenance = () => {
const { menuItemId } = useParams();
const [subMenus, setSubMenus] = useState([]);
const [showAddModal, setShowAddModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [currentItem, setCurrentItem] = useState({
menuItemId: "",
menuItemDesc: "",
itemSeq: "",
moduleName: "",
main_menu_action_name: "#",
status: "Enable",
main_menu_icon_name: "fa-circle"
});
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchSubMenus();
}, [menuItemId]);
const fetchSubMenus = async () => {
setIsLoading(true);
try {
const data = await getSubmenuItems(menuItemId);
setSubMenus(data);
} catch (err) {
toast.error("Failed to fetch submenu items");
console.error(err);
} finally {
setIsLoading(false);
}
};
const handleAdd = async () => {
try {
const addedItem = await addSubmenuItem(menuItemId, currentItem);
setSubMenus([...subMenus, addedItem]);
resetForm();
setShowAddModal(false);
toast.success("Submenu added successfully!");
} catch (err) {
toast.error(err.message || "Failed to add submenu");
}
};
const handleEdit = (item) => {
setCurrentItem({
...item,
status: item.status ? "Enable" : "Disable"
});
setShowEditModal(true);
};
const handleUpdate = async () => {
try {
const updatedItem = await updateSubmenuItem(currentItem.menuItemId, currentItem);
setSubMenus(subMenus.map(item =>
item.menuItemId === updatedItem.menuItemId ? updatedItem : item
));
resetForm();
setShowEditModal(false);
toast.success("Submenu updated successfully!");
} catch (err) {
toast.error(err.message || "Failed to update submenu");
}
};
const handleDelete = async (id) => {
if (window.confirm("Are you sure you want to delete this submenu?")) {
try {
await deleteSubmenuItem(id);
setSubMenus(subMenus.filter(item => item.menuItemId !== id));
toast.success("Submenu deleted successfully!");
} catch (err) {
toast.error(err.message || "Failed to delete submenu");
}
}
};
const resetForm = () => {
setCurrentItem({
menuItemId: "",
menuItemDesc: "",
itemSeq: "",
moduleName: "",
main_menu_action_name: "#",
status: "Enable",
main_menu_icon_name: "fa-circle"
});
};
return (
<Box sx={{ p: 4 }}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 4
}}>
<Typography variant="h4" component="h2">
Sub-Menus for Menu ID: {menuItemId}
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setShowAddModal(true)}
>
ADD NEW
</Button>
</Box>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>No.</TableCell>
<TableCell>Sub-Menu Item Name</TableCell>
<TableCell>ID</TableCell>
<TableCell>Sequence</TableCell>
<TableCell>Module Name</TableCell>
<TableCell>Menu Action Link</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={8} align="center">
<CircularProgress />
</TableCell>
</TableRow>
) : subMenus.length === 0 ? (
<TableRow>
<TableCell colSpan={8} align="center">
No submenus found
</TableCell>
</TableRow>
) : (
subMenus.map((item, index) => (
<TableRow key={item.menuItemId}>
<TableCell>{index + 1}</TableCell>
<TableCell>{item.menuItemDesc}</TableCell>
<TableCell>{item.menuItemId}</TableCell>
<TableCell>{item.itemSeq}</TableCell>
<TableCell>{item.moduleName}</TableCell>
<TableCell>{item.main_menu_action_name}</TableCell>
<TableCell>{item.status ? "Enable" : "Disable"}</TableCell>
<TableCell>
<IconButton onClick={() => handleEdit(item)}>
<EditIcon />
</IconButton>
<IconButton onClick={() => handleDelete(item.menuItemId)}>
<DeleteIcon color="error" />
</IconButton>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{/* Add Submenu Modal */}
<Dialog open={showAddModal} onClose={() => setShowAddModal(false)}>
<DialogTitle>Add Submenu</DialogTitle>
<DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
<TextField
label="Sub-Menu Item Name"
value={currentItem.menuItemDesc}
onChange={(e) => setCurrentItem({...currentItem, menuItemDesc: e.target.value})}
required
fullWidth
margin="normal"
/>
<TextField
label="Sequence"
type="number"
value={currentItem.itemSeq}
onChange={(e) => setCurrentItem({...currentItem, itemSeq: e.target.value})}
required
fullWidth
margin="normal"
/>
<TextField
label="Module Name"
value={currentItem.moduleName}
onChange={(e) => setCurrentItem({...currentItem, moduleName: e.target.value})}
fullWidth
margin="normal"
/>
<TextField
label="Menu Action Link"
value={currentItem.main_menu_action_name}
onChange={(e) => setCurrentItem({...currentItem, main_menu_action_name: e.target.value})}
fullWidth
margin="normal"
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowAddModal(false)}>CANCEL</Button>
<Button onClick={handleAdd} variant="contained">ADD</Button>
</DialogActions>
</Dialog>
{/* Edit Submenu Modal */}
<Dialog open={showEditModal} onClose={() => setShowEditModal(false)}>
<DialogTitle>Update Sub Menu Maintenance</DialogTitle>
<DialogContent>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
<TextField
label="Menu ID"
value={menuItemId}
disabled
fullWidth
margin="normal"
/>
<TextField
label="Menu Item Name"
value={currentItem.menuItemDesc}
onChange={(e) => setCurrentItem({...currentItem, menuItemDesc: e.target.value})}
required
fullWidth
margin="normal"
/>
<TextField
label="Sequence"
type="number"
value={currentItem.itemSeq}
onChange={(e) => setCurrentItem({...currentItem, itemSeq: e.target.value})}
required
fullWidth
margin="normal"
/>
<TextField
label="Module Name"
value={currentItem.moduleName}
onChange={(e) => setCurrentItem({...currentItem, moduleName: e.target.value})}
fullWidth
margin="normal"
/>
<TextField
label="Menu Action Link"
value={currentItem.main_menu_action_name}
onChange={(e) => setCurrentItem({...currentItem, main_menu_action_name: e.target.value})}
fullWidth
margin="normal"
/>
<Select
value={currentItem.status}
onChange={(e) => setCurrentItem({...currentItem, status: e.target.value})}
fullWidth
margin="dense"
>
<MenuItem value="Enable">Enable</MenuItem>
<MenuItem value="Disable">Disable</MenuItem>
</Select>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowEditModal(false)}>CANCEL</Button>
<Button onClick={handleUpdate} variant="contained">UPDATE</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default SubMenuMaintenance;

View File

@@ -1,335 +1,111 @@
import React, { useState, useEffect } from 'react';
import { Eye, EyeOff, User, Lock, Mail, Camera, UserPlus, ArrowLeft, Check } from 'lucide-react';
// Mock navigation function for demo
const mockNavigate = (path) => {
console.log(`Navigating to: ${path}`);
alert(`Would navigate to: ${path}`);
};
const CreateAccountPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [reEnterPassword, setReEnterPassword] = useState('');
const [avatarImage, setAvatarImage] = useState(null);
const [showPassword, setShowPassword] = useState(false);
const [showReEnterPassword, setShowReEnterPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [mounted, setMounted] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [passwordStrength, setPasswordStrength] = useState(0);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
// Calculate password strength
let strength = 0;
if (password.length >= 8) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^A-Za-z0-9]/.test(password)) strength++;
setPasswordStrength(strength);
}, [password]);
const handleCreateAccount = async (e) => {
e.preventDefault();
setErrorMessage('');
setIsLoading(true);
if (!email || !password || !reEnterPassword) {
setErrorMessage('All fields are required.');
setIsLoading(false);
return;
}
if (password !== reEnterPassword) {
setErrorMessage('Passwords do not match.');
setIsLoading(false);
return;
}
if (password.length < 8) {
setErrorMessage('Password must be at least 8 characters long.');
setIsLoading(false);
return;
}
// Simulate API call
setTimeout(() => {
console.log('Account created successfully!');
alert('Account created successfully!');
mockNavigate('/login');
setIsLoading(false);
}, 2000);
};
const handleAvatarChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setAvatarImage(reader.result);
};
reader.readAsDataURL(file);
}
};
const getPasswordStrengthColor = () => {
if (passwordStrength === 0) return 'bg-gray-300';
if (passwordStrength === 1) return 'bg-red-400';
if (passwordStrength === 2) return 'bg-yellow-400';
if (passwordStrength === 3) return 'bg-blue-400';
return 'bg-green-400';
};
const getPasswordStrengthText = () => {
if (passwordStrength === 0) return 'Enter password';
if (passwordStrength === 1) return 'Weak';
if (passwordStrength === 2) return 'Fair';
if (passwordStrength === 3) return 'Good';
return 'Strong';
};
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-purple-100 relative overflow-hidden">
{/* Background Elements */}
<div className="absolute inset-0">
{/* Subtle geometric shapes */}
<div className="absolute top-20 left-20 w-32 h-32 bg-purple-200 rounded-full opacity-20 animate-pulse"></div>
<div className="absolute bottom-20 right-20 w-24 h-24 bg-purple-300 rounded-full opacity-20 animate-pulse" style={{ animationDelay: '1s' }}></div>
<div className="absolute top-1/2 right-10 w-16 h-16 bg-purple-400 rounded-full opacity-15 animate-pulse" style={{ animationDelay: '2s' }}></div>
{/* Grid pattern */}
<div className="absolute inset-0 opacity-5" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%236B46C1' fill-opacity='0.4'%3E%3Cpath d='M20 20h20v20H20V20zm-20 0h20v20H0V20z'/%3E%3C/g%3E%3C/svg%3E")`
}}></div>
</div>
{/* Main Content */}
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
{/* Background Logo */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
</div>
</div>
{/* Create Account Card */}
<div className={`relative w-full max-w-lg transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
{/* Card Background */}
<div className="bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
{/* Header Section */}
<div className="bg-gradient-to-r from-purple-600 to-purple-700 p-8 text-center relative">
<div className="absolute top-4 left-4">
<button
onClick={() => mockNavigate('/login')}
className="p-2 text-white hover:bg-white hover:bg-opacity-20 rounded-xl transition-all duration-200"
>
<ArrowLeft className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
<div className="inline-flex items-center justify-center w-16 h-16 bg-white bg-opacity-20 rounded-2xl">
<UserPlus className="w-8 h-8 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-white mb-2">Create Account</h1>
<p className="text-purple-100">Join our community today</p>
</div>
</div>
</div>
{/* Form Section */}
<div className="p-8 space-y-6">
{/* Error Message */}
{errorMessage && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm">
{errorMessage}
</div>
)}
{/* Avatar Upload */}
<div className="text-center">
<div className="relative inline-block">
<input
accept="image/*"
id="avatar-input"
type="file"
className="hidden"
onChange={handleAvatarChange}
/>
<label htmlFor="avatar-input" className="cursor-pointer group">
<div className="relative">
<img
alt="Avatar"
src={avatarImage || 'https://via.placeholder.com/120/E5E7EB/6B7280?text=Photo'}
className="w-28 h-28 rounded-full border-4 border-purple-100 group-hover:border-purple-300 transition-all duration-200 object-cover"
/>
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 rounded-full transition-all duration-200 flex items-center justify-center">
<Camera className="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
</div>
</div>
</label>
</div>
<p className="text-sm text-gray-500 mt-2">Click to upload your photo</p>
</div>
{/* Form Fields */}
<div className="space-y-5">
{/* Email Field */}
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Email Address</label>
<div className="relative group">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<Mail className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
</div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
placeholder="Enter your email"
/>
</div>
</div>
{/* Password Field */}
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Password</label>
<div className="relative group">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-12 pr-12 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
placeholder="Create a strong password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
{/* Password Strength Indicator */}
{password && (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<div className="flex-1 bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${getPasswordStrengthColor()}`}
style={{ width: `${(passwordStrength / 4) * 100}%` }}
></div>
</div>
<span className="text-xs font-medium text-gray-600">
{getPasswordStrengthText()}
</span>
</div>
<div className="text-xs text-gray-500 space-y-1">
<div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${password.length >= 8 ? 'bg-green-400' : 'bg-gray-300'}`}></div>
<span>At least 8 characters</span>
</div>
<div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${/[A-Z]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
<span>One uppercase letter</span>
</div>
<div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${/[0-9]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
<span>One number</span>
</div>
</div>
</div>
)}
</div>
{/* Re-enter Password Field */}
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Confirm Password</label>
<div className="relative group">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
</div>
<input
type={showReEnterPassword ? 'text' : 'password'}
value={reEnterPassword}
onChange={(e) => setReEnterPassword(e.target.value)}
className={`w-full pl-12 pr-12 py-4 bg-gray-50 border rounded-xl focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 ${
reEnterPassword && password !== reEnterPassword
? 'border-red-300 focus:ring-red-500'
: reEnterPassword && password === reEnterPassword
? 'border-green-300 focus:ring-green-500'
: 'border-gray-200 focus:ring-purple-500'
}`}
placeholder="Confirm your password"
/>
<button
type="button"
onClick={() => setShowReEnterPassword(!showReEnterPassword)}
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
>
{showReEnterPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
{reEnterPassword && password === reEnterPassword && (
<div className="absolute right-12 top-1/2 transform -translate-y-1/2">
<Check className="w-5 h-5 text-green-500" />
</div>
)}
</div>
{reEnterPassword && password !== reEnterPassword && (
<p className="text-xs text-red-500">Passwords do not match</p>
)}
</div>
</div>
{/* Create Account Button */}
<button
type="button"
onClick={handleCreateAccount}
disabled={isLoading || !email || !password || !reEnterPassword || password !== reEnterPassword}
className="w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-purple-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none disabled:cursor-not-allowed"
>
<div className="flex items-center justify-center space-x-2">
{isLoading ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
<span>Creating Account...</span>
</>
) : (
<>
<UserPlus className="w-5 h-5" />
<span>Create Account</span>
</>
)}
</div>
</button>
{/* Login Link */}
<div className="text-center pt-4 border-t border-gray-100">
<p className="text-gray-600 mb-3">Already have an account?</p>
<button
type="button"
onClick={() => mockNavigate('/login')}
className="inline-flex items-center space-x-2 text-purple-600 hover:text-purple-700 font-semibold transition-colors"
>
<User className="w-4 h-4" />
<span>Sign In</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CreateAccountPage;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AccountCircle, Visibility, VisibilityOff } from '@mui/icons-material';
import './Login.css'; // Import CSS file for custom styling
const CreateAccountPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [reEnterPassword, setReEnterPassword] = useState('');
const [avatarImage, setAvatarImage] = useState(null);
const navigate = useNavigate();
const handleCreateAccount = (e) => {
e.preventDefault();
// Your create account logic here
};
const handleAvatarChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setAvatarImage(reader.result);
};
reader.readAsDataURL(file);
}
};
return (
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
CLOUDNSURE
</div>
</div>
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10">
<div className="flex items-center justify-center mb-6">
<AccountCircle className="text-7xl text-gray-700" />
</div>
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Create Account</h2>
<form onSubmit={handleCreateAccount} className="space-y-4">
<div className="flex items-center justify-center">
<input
accept="image/*"
id="avatar-input"
type="file"
className="hidden"
onChange={handleAvatarChange}
/>
<label htmlFor="avatar-input" className="cursor-pointer">
<img
alt="Avatar"
src={avatarImage || 'https://via.placeholder.com/120'}
className="w-28 h-28 rounded-full mb-4"
/>
</label>
</div>
<div>
<label className="block text-gray-600">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
/>
</div>
<div>
<label className="block text-gray-600">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
/>
</div>
<div>
<label className="block text-gray-600">Re-enter Password</label>
<input
type="password"
value={reEnterPassword}
onChange={(e) => setReEnterPassword(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
/>
</div>
<div className="text-center">
<button
type="submit"
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300 transition duration-300 ease-in-out"
>
Create Account
</button>
</div>
</form>
<div className="mt-4 text-center">
<p className="text-gray-600">
Already have an account?{' '}
<button
type="button"
onClick={() => navigate('/login')}
className="text-blue-600 hover:underline focus:outline-none"
>
Log in
</button>
</p>
</div>
</div>
</div>
);
};
export default CreateAccountPage;

View File

@@ -1,194 +1,99 @@
import React, { useState, useEffect } from 'react';
import { Mail, ArrowLeft, Shield, CheckCircle, AlertCircle, Sparkles } from 'lucide-react';
const ForgotPasswordPage = () => {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [mounted, setMounted] = useState(false);
const [particles, setParticles] = useState([]);
useEffect(() => {
setMounted(true);
const newParticles = Array.from({ length: 35 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 3 + 1,
duration: Math.random() * 4 + 3,
delay: Math.random() * 2,
}));
setParticles(newParticles);
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setMessage('');
setMessageType('');
try {
await new Promise(resolve => setTimeout(resolve, 2000));
if (email.includes('@')) {
setMessage('Reset password email sent successfully. Please check your email.');
setMessageType('success');
setEmail('');
} else {
setMessage('Please enter a valid email address.');
setMessageType('error');
}
} catch (error) {
setMessage(`Error during reset password: ${error.message}`);
setMessageType('error');
} finally {
setIsLoading(false);
}
};
const handleBackToLogin = () => {
alert('Back to login functionality would be implemented here');
};
return (
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
{particles.map((particle) => (
<div
key={particle.id}
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
style={{
left: `${particle.x}%`,
top: `${particle.y}%`,
width: `${particle.size}px`,
height: `${particle.size}px`,
animationDuration: `${particle.duration}s`,
animationDelay: `${particle.delay}s`,
}}
/>
))}
<div className="absolute inset-0 opacity-20" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
}}></div>
</div>
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
<div className="flex justify-center mt-4 space-x-2">
{[...Array(5)].map((_, i) => (
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
))}
</div>
</div>
</div>
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
<div className="relative p-8 space-y-6">
<button
onClick={handleBackToLogin}
className="group absolute top-6 left-6 p-2 text-gray-400 hover:text-purple-600 transition-colors rounded-full hover:bg-purple-50"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
</button>
<div className="text-center space-y-4 pt-8">
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
<Shield className="w-10 h-10 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-800 mb-2">Forgot Password?</h1>
<p className="text-gray-600 leading-relaxed">Don't worry! Enter your email address and we'll send you a secure link to reset your password.</p>
</div>
</div>
{message && (
<div className={`rounded-xl p-4 text-sm animate-pulse flex items-center space-x-3 ${
messageType === 'error'
? 'bg-red-50 border border-red-200 text-red-700'
: 'bg-green-50 border border-green-200 text-green-700'
}`}>
{messageType === 'error' ? (
<AlertCircle className="w-5 h-5 flex-shrink-0" />
) : (
<CheckCircle className="w-5 h-5 flex-shrink-0" />
)}
<span>{message}</span>
</div>
)}
<div className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Email Address</label>
<div className="relative group">
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<Mail className="w-5 h-5 text-gray-400" />
</div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
placeholder="Enter your email address"
required
autoFocus
/>
</div>
</div>
</div>
<button
type="submit"
onClick={handleSubmit}
disabled={isLoading}
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
>
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="relative flex items-center justify-center space-x-2">
{isLoading ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
<span>Sending Reset Link...</span>
</>
) : (
<>
<Mail className="w-5 h-5" />
<span>Send Reset Link</span>
</>
)}
</div>
</button>
</div>
<div className="text-center pt-4 border-t border-gray-200">
<p className="text-gray-600 mb-3">Remember your password?</p>
<button
type="button"
onClick={handleBackToLogin}
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
>
<ArrowLeft className="w-4 h-4 text-purple-600 group-hover:text-purple-700 group-hover:-translate-x-1 transition-all" />
<span>Back to Sign In</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ForgotPasswordPage;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AccountCircle } from '@mui/icons-material';
import './Login.css'; // Import CSS file for custom styling
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
const API_FORGOT_PASSWORD = `${API_BASE_URL}/backend/api/resources/forgotpassword`;
const ForgotPasswordPage = () => {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState(''); // State to manage message type for styling
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch(API_FORGOT_PASSWORD, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (!response.ok) {
const errorText = await response.text();
setMessage(`Reset password failed: ${errorText}`);
setMessageType('error');
return;
}
setMessage('Reset password email sent successfully. Please check your email.');
setMessageType('success');
} catch (error) {
setMessage(`Error during reset password: ${error.message}`);
setMessageType('error');
console.error('Error during reset password:', error);
}
};
return (
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
CLOUDNSURE
</div>
</div>
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10">
<div className="flex items-center justify-center mb-6">
<AccountCircle className="text-7xl text-gray-700" />
</div>
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Forgot Password</h2>
<p className="text-center text-gray-600 mb-4">
Enter your email address and we'll send you a link to reset your password.
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-gray-600">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
required
autoFocus
/>
</div>
<div className="text-center">
<button
type="submit"
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300 transition duration-300 ease-in-out"
>
Reset Password
</button>
</div>
</form>
{message && (
<div className={`mt-4 text-center ${messageType === 'error' ? 'text-red-500' : 'text-green-500'}`}>
{message}
</div>
)}
<p className="mt-4 text-center text-gray-600">
Remember your password?{' '}
<button
type="button"
onClick={() => navigate('/login')}
className="text-blue-600 hover:underline focus:outline-none"
>
Log in
</button>
</p>
</div>
</div>
);
};
export default ForgotPasswordPage;

View File

@@ -1,64 +1,64 @@
/* LoginPage.css */
.login-container {
background: linear-gradient(to bottom, #417cd5, white); /* Linear gradient from blue to white */
height: 100vh; /* Full viewport height */
display: flex;
}
.login-box {
display: flex;
background-color: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
height: 500px;
}
.login-left {
background-color: #31c2db;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
padding: 4rem; /* Padding for better spacing */
}
.login-left img {
width: 80px;
}
.login-right {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 4rem; /* Padding for better spacing */
}
.login-form {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
}
.forgot-password, .create-account {
margin-top: 1rem; /* Spacing between links and other elements */
}
.login-form .MuiButton-root {
background-color: #1fcf90;
color: white;
margin-top: 1rem; /* Spacing at the top of the button */
}
.MuiAlert-root {
margin-bottom: 1rem; /* Spacing below the alert */
}
.forgot-password {
text-align: right;
margin-top: -10px;
}
/* LoginPage.css */
.login-container {
background: linear-gradient(to bottom, #417cd5, white); /* Linear gradient from blue to white */
height: 100vh; /* Full viewport height */
display: flex;
}
.login-box {
display: flex;
background-color: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
height: 500px;
}
.login-left {
background-color: #31c2db;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
padding: 4rem; /* Padding for better spacing */
}
.login-left img {
width: 80px;
}
.login-right {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 4rem; /* Padding for better spacing */
}
.login-form {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
}
.forgot-password, .create-account {
margin-top: 1rem; /* Spacing between links and other elements */
}
.login-form .MuiButton-root {
background-color: #1fcf90;
color: white;
margin-top: 1rem; /* Spacing at the top of the button */
}
.MuiAlert-root {
margin-bottom: 1rem; /* Spacing below the alert */
}
.forgot-password {
text-align: right;
margin-top: -10px;
}

View File

@@ -1,238 +1,156 @@
import React, { useState, useEffect } from 'react';
import { Eye, EyeOff, User, Lock, Mail, Sparkles, Shield, ArrowRight } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
const LoginPage = () => {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [mounted, setMounted] = useState(false);
const [particles, setParticles] = useState([]);
useEffect(() => {
setMounted(true);
const newParticles = Array.from({ length: 40 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 3 + 1,
duration: Math.random() * 4 + 3,
delay: Math.random() * 2,
}));
setParticles(newParticles);
}, []);
const handleLogin = async (e) => {
e.preventDefault();
setErrorMessage('');
setIsLoading(true);
if (!username || !password) {
setErrorMessage('Username and password are required.');
setIsLoading(false);
return;
}
const storedCredentials = JSON.parse(localStorage.getItem('userCredentials') || '{}');
if ((username === 'sysadmin' && password === 'test3') ||
(storedCredentials[username] && storedCredentials[username] === password)) {
localStorage.setItem('authToken', 'dummy-token');
localStorage.setItem('currentUser', username);
navigate('/Dashboard');
} else {
setErrorMessage('Invalid username or password. Please use sysadmin/test3 or your registered credentials.');
}
setIsLoading(false);
};
const handleForgotPassword = () => {
navigate('/ForgotPassword');
};
const handleCreateAccount = () => {
navigate('/CreateAccount');
};
return (
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
{particles.map((particle) => (
<div
key={particle.id}
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
style={{
left: `${particle.x}%`,
top: `${particle.y}%`,
width: `${particle.size}px`,
height: `${particle.size}px`,
animationDuration: `${particle.duration}s`,
animationDelay: `${particle.delay}s`,
}}
/>
))}
<div className="absolute inset-0 opacity-20" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
}}></div>
</div>
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
<div className="flex justify-center mt-4 space-x-2">
{[...Array(5)].map((_, i) => (
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
))}
</div>
</div>
</div>
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
<div className="relative p-8 space-y-6">
<div className="text-center space-y-4">
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
<Shield className="w-10 h-10 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-800 mb-2">Welcome Back</h1>
<p className="text-gray-600">Sign in to your account</p>
</div>
</div>
{errorMessage && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm animate-pulse">
{errorMessage}
</div>
)}
<div className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Username</label>
<div className="relative group">
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<User className="w-5 h-5 text-gray-400" />
</div>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
placeholder="Enter your username"
/>
</div>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Password</label>
<div className="relative group">
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
<Lock className="w-5 h-5 text-gray-400" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-12 pr-12 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
placeholder="Enter your password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-purple-600 transition-colors"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
</div>
<div className="flex items-center justify-between">
<label className="flex items-center space-x-3 cursor-pointer group">
<div className="relative">
<input
type="checkbox"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
className="sr-only"
/>
<div className={`w-5 h-5 rounded border-2 transition-all ${rememberMe ? 'bg-gradient-to-r from-purple-500 to-indigo-600 border-purple-500' : 'border-gray-300 group-hover:border-purple-400'}`}>
{rememberMe && (
<svg className="w-3 h-3 text-white absolute top-0.5 left-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</div>
</div>
<span className="text-sm text-gray-600 group-hover:text-purple-700 transition-colors">Remember me</span>
</label>
<button
type="button"
onClick={handleForgotPassword}
className="text-sm text-purple-600 hover:text-purple-700 transition-colors font-medium"
>
Forgot password?
</button>
</div>
<button
type="button"
onClick={handleLogin}
disabled={isLoading}
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
>
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="relative flex items-center justify-center space-x-2">
{isLoading ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
<span>Signing in...</span>
</>
) : (
<>
<span>Sign In</span>
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</>
)}
</div>
</button>
</div>
<div className="text-center pt-4 border-t border-gray-200">
<p className="text-gray-600 mb-3">Don't have an account?</p>
<button
type="button"
onClick={handleCreateAccount}
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
>
<User className="w-4 h-4 text-purple-600 group-hover:text-purple-700" />
<span>Create New Account</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AccountCircle, Visibility, VisibilityOff } from '@mui/icons-material';
import './Login.css'; // Import CSS file for custom styling
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
const API_TOKEN_SESSION = `${API_BASE_URL}/token/session`;
const LoginPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
setErrorMessage('');
if (!email || !password) {
setErrorMessage('Email and password are required.');
return;
}
try {
const response = await fetch(API_TOKEN_SESSION, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Login failed:', errorText);
setErrorMessage('Login failed. Please check your credentials.');
return;
}
const data = await response.json();
console.log('Login response:', data);
if (data.operationStatus !== 'SUCCESS') {
console.error('Login failed:', data.operationMessage);
setErrorMessage(data.operationMessage || 'Login failed. Please try again.');
return;
}
localStorage.setItem('authToken', data.item.token);
localStorage.setItem('user', JSON.stringify(data.item));
console.log('Token stored in local storage:', data.item.token);
navigate('/dashboard');
} catch (error) {
console.error('Error during login:', error);
setErrorMessage('An error occurred during login. Please try again later.');
}
};
const handleForgotPassword = () => {
navigate('/ForgotPassword');
};
const handleCreateAccount = () => {
navigate('/CreateAccount');
};
return (
<div className="relative min-h-screen flex items-center justify-center bg-gray-700">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-gray-100 text-9xl font-bold opacity-75">
CLOUDNSURE
</div>
</div>
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10 ">
<div className="flex items-center justify-center mb-6">
<AccountCircle className="text-7xl text-gray-700" />
</div>
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4 ">Log in</h2>
{errorMessage && <div className="mb-4 text-red-600">{errorMessage}</div>}
<form onSubmit={handleLogin} className="space-y-4">
<div>
<label className="block text-gray-600">Email</label>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
/>
</div>
<div>
<label className="block text-gray-600">Password</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className=""
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</button>
</div>
</div>
</div>
<div className="flex items-center justify-between">
<label className="flex items-center">
<input
type="checkbox"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
className="form-checkbox"
/>
<span className="ml-2 text-gray-600">Remember Me</span>
</label>
<button
type="button"
onClick={handleForgotPassword}
className="text-sm text-blue-600 hover:underline focus:outline-none"
>
Forgot password?
</button>
</div>
<div className="text-center">
<button
type="submit"
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300"
>
Login
</button>
</div>
</form>
<div className="mt-4 text-center">
<p className="text-gray-600">
Don't have an account?{' '}
<button
type="button"
onClick={handleCreateAccount}
className="text-blue-600 hover:underline focus:outline-none"
>
Create Account
</button>
</p>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -0,0 +1,106 @@
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
IconButton,
Typography,
Box
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
/**
* A reusable confirmation modal component using Material-UI
* @param {Object} props - Component props
* @param {boolean} props.open - Controls modal visibility
* @param {Function} props.onClose - Function to call when modal is closed
* @param {Function} props.onConfirm - Function to call when action is confirmed
* @param {string} props.title - Modal title
* @param {string} props.message - Confirmation message
* @param {string} props.confirmLabel - Label for the confirm button
* @param {string} props.cancelLabel - Label for the cancel button
* @param {string} props.confirmColor - Color for the confirm button ('primary', 'secondary', 'error', etc.)
*/
const ConfirmModal = ({
open,
onClose,
onConfirm,
title = "Confirm Action",
message = "Are you sure you want to proceed?",
confirmLabel = "Confirm",
cancelLabel = "Cancel",
confirmColor = "error"
}) => {
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
borderRadius: 2
}
}}
>
<DialogTitle sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: 'primary.main',
borderBottom: '1px solid',
borderColor: 'divider'
}}>
{title}
<IconButton
onClick={onClose}
size="small"
sx={{
color: 'text.secondary',
'&:hover': {
color: 'text.primary'
}
}}
aria-label="close"
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent sx={{ pt: 3 }}>
<Typography variant="body1">{message}</Typography>
</DialogContent>
<DialogActions sx={{
justifyContent: 'space-between',
px: 3,
py: 2,
borderTop: '1px solid',
borderColor: 'divider'
}}>
<Button
variant="outlined"
onClick={onClose}
sx={{ px: 4 }}
>
{cancelLabel}
</Button>
<Button
variant="contained"
color={confirmColor}
onClick={() => {
onConfirm();
onClose();
}}
sx={{ px: 4 }}
>
{confirmLabel}
</Button>
</DialogActions>
</Dialog>
);
};
export default ConfirmModal;

View File

@@ -0,0 +1,159 @@
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import {
Box,
Button,
Card,
CardContent,
Checkbox,
FormControl,
FormControlLabel,
TextField,
Typography,
Chip
} from '@mui/material';
import {
Save as SaveIcon,
ArrowBack as ArrowBackIcon
} from '@mui/icons-material';
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
const DashboardbuilderAdd = () => {
const [moduleId, setModuleId] = useState(null);
const navigate = useNavigate();
const { register, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
dashboard_name: "",
description: "",
secuirity_profile: "",
module_id: null,
tech_Stack: "",
object_type: "",
sub_object_type: "",
add_to_home: true,
dashbord1_Line: [{
model: JSON.stringify({
dashboard: [{
cols: 4,
rows: 5,
x: 0,
y: 0,
name: "Radar Chart",
component: "Radar Chart",
}],
})
}],
},
});
useEffect(() => {
setModuleId("exampleModuleId");
}, []);
const onSubmit = (data) => {
if (!data.dashboard_name || !data.secuirity_profile || !data.description) {
toast.error("Please fill out all required fields.");
return;
}
const newDashboard = {
...data,
};
DashboardBuilderService.create(newDashboard)
.then((response) => {
toast.success("Dashboard Added successfully");
navigate("/dashboard/dashboard-new-all");
})
.catch((error) => {
console.error(error);
toast.error("Error while adding dashboard");
});
};
return (
<Card sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
<Typography variant="h5" sx={{ fontWeight: 500 }}>
Define Dashboard
</Typography>
<Chip
label="Add Mode"
color="primary"
size="small"
sx={{ ml: 2 }}
/>
</Box>
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 3, mb: 3 }}>
<FormControl fullWidth>
<TextField
label="Dashboard Name"
variant="outlined"
{...register("dashboard_name", { required: true })}
error={!!errors.dashboard_name}
helperText={errors.dashboard_name ? "This field is required" : ""}
/>
</FormControl>
<FormControl fullWidth>
<TextField
label="Security Profile"
variant="outlined"
{...register("secuirity_profile", { required: true })}
error={!!errors.secuirity_profile}
helperText={errors.secuirity_profile ? "This field is required" : ""}
/>
</FormControl>
<FormControl fullWidth sx={{ gridColumn: '1 / -1' }}>
<TextField
label="Description"
variant="outlined"
multiline
rows={3}
{...register("description", { required: true })}
error={!!errors.description}
helperText={errors.description ? "This field is required" : ""}
/>
</FormControl>
<FormControlLabel
control={
<Checkbox
defaultChecked
{...register("add_to_home")}
color="primary"
/>
}
label="Add to Dashboard"
/>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={() => navigate("/dashboard/dashboard-new-all")}
>
Back
</Button>
<Button
type="submit"
variant="contained"
color="primary"
startIcon={<SaveIcon />}
>
Submit
</Button>
</Box>
</Box>
</Card>
);
};
export default DashboardbuilderAdd;

View File

@@ -0,0 +1,213 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import * as XLSX from "xlsx";
import { toast } from "react-toastify";
import {
Box,
Button,
Card,
CardContent,
Typography,
CircularProgress,
Alert,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Modal,
Tooltip
} from "@mui/material";
import {
Add as AddIcon,
Delete as DeleteIcon,
Edit as EditIcon,
GridOn as GridIcon,
FileDownload as ExportIcon,
Schedule as TimeIcon
} from "@mui/icons-material";
import DashboardBuilderService from "../../../src/ApiServices/DashboardBuilderServices";
import ConfirmModal from "../common/ConfirmModel";
const DashboardNewAll = () => {
const { t: translate } = useTranslation();
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [selectedRow, setSelectedRow] = useState(null);
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
fetchAllDashboards();
}, []);
const fetchAllDashboards = async () => {
try {
setLoading(true);
const dashboards = await DashboardBuilderService.getAllDash();
setData(dashboards);
toast.success("Dashboards fetched successfully.");
} catch (error) {
console.error('Error fetching dashboards:', error);
toast.error('Failed to fetch dashboards.');
setError(error.message);
} finally {
setLoading(false);
}
};
const handleAddClick = () => {
navigate("/dashboard/dashboard-new-add");
};
const handleRunner = () => {
navigate("/dashboard/dashboard-runner-all");
};
const handleEditClick = (dashboard) => {
navigate(`/dashboard/dashboard-new-edit/${dashboard.id}`, { state: { dashboard } });
};
const handleDeleteClick = (row) => {
setSelectedRow(row);
setShowConfirmModal(true);
};
const handleDeleteConfirm = async () => {
try {
await DashboardBuilderService.deleteField(selectedRow.id);
setData(prevData => prevData.filter(dashboard => dashboard.id !== selectedRow.id));
toast.success("Dashboard deleted successfully!");
} catch (error) {
console.error("Error deleting dashboard field:", error);
toast.error("Failed to delete dashboard. Please try again.");
}
setShowConfirmModal(false);
};
const handleExport = () => {
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(data);
XLSX.utils.book_append_sheet(workbook, worksheet, "Dashboards");
XLSX.writeFile(workbook, "dashboards_export.xlsx");
};
const handleSetUp = (id) => {
navigate(`/dashboard/edit-new-dash/${id}`, { dashboardId: id });
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
<Typography variant="h4">{translate("Dashboard Builder")}</Typography>
<Box sx={{ display: "flex", gap: 2 }}>
<Button
variant="contained"
startIcon={<GridIcon />}
onClick={handleRunner}
>
{translate("Dashboard_runner")}
</Button>
<Button
variant="outlined"
startIcon={<ExportIcon />}
onClick={handleExport}
>
{translate("EXPORT_XLSX")}
</Button>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleAddClick}
>
{translate("ADD")}
</Button>
</Box>
</Box>
{loading ? (
<Box sx={{ display: "flex", justifyContent: "center", my: 4 }}>
<CircularProgress />
</Box>
) : error ? (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
) : data.length === 0 ? (
<Alert severity="info">No dashboards available.</Alert>
) : (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Go To</TableCell>
<TableCell>Dashboard Name</TableCell>
<TableCell>Description</TableCell>
<TableCell>Security Profile</TableCell>
<TableCell>Add to Home</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((dashboard) => (
<TableRow key={dashboard.id}>
<TableCell>
<Button
variant="outlined"
size="small"
onClick={() => handleSetUp(dashboard.id)}
>
SET UP
</Button>
</TableCell>
<TableCell>{dashboard.dashboard_name}</TableCell>
<TableCell>{dashboard.description}</TableCell>
<TableCell>{dashboard.secuirity_profile}</TableCell>
<TableCell>{dashboard.add_to_home ? "Yes" : "No"}</TableCell>
<TableCell>
<Tooltip title="Edit">
<IconButton
color="primary"
onClick={() => handleEditClick(dashboard)}
>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton
color="error"
onClick={() => handleDeleteClick(dashboard)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
<ConfirmModal
open={showConfirmModal}
onClose={() => setShowConfirmModal(false)}
onConfirm={handleDeleteConfirm}
title="Delete Dashboard"
message={`Are you sure you want to delete "${selectedRow?.dashboard_name || 'this dashboard'}"?`}
confirmText="Delete"
cancelText="Cancel"
confirmColor="error"
/>
</Box>
);
};
export default DashboardNewAll;

View File

@@ -0,0 +1,278 @@
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import {
Box,
Button,
Card,
CardContent,
CardHeader,
CircularProgress,
Typography,
useTheme
} from '@mui/material';
import { ArrowBack as ArrowBackIcon } from '@mui/icons-material';
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
// Import chart components
import LineChartComponent from '../gadgets/linechart';
import PieChartComponent from '../gadgets/piechart';
import PolarChartComponent from '../gadgets/polarchart';
import RadarChartComponent from '../gadgets/radarchart';
import BubbleChart from '../gadgets/bubblechart';
import BarChart from '../gadgets/barchart';
import DoughnutChart from '../gadgets/doughnut';
import DynamicChart from '../gadgets/dynamicchart';
import FinancialChart from '../gadgets/financialchart';
import GridViewComponent from '../gadgets/gridview';
import ScatterChartComponent from '../gadgets/scatterchart';
import ToDoChartComponent from '../gadgets/todochart';
const ViewDashboard = () => {
const { id } = useParams();
const theme = useTheme();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [dashboard, setDashboard] = useState(null);
const [layout, setLayout] = useState([]);
const componentMap = {
'radar_chart': RadarChartComponent,
'polar_chart': PolarChartComponent,
'pie_chart': PieChartComponent,
'bar_chart': BarChart,
'bubble_chart': BubbleChart,
'line_chart': LineChartComponent,
'doughnut_chart': DoughnutChart,
'dynamic_chart': DynamicChart,
'financial_chart': FinancialChart,
'grid_view': GridViewComponent,
'scatter_chart': ScatterChartComponent,
'todo_chart': ToDoChartComponent
};
useEffect(() => {
const fetchDashboard = async () => {
try {
setLoading(true);
const response = await DashboardBuilderService.getById(id);
setDashboard({
name: response.dashboard_name,
dashboardLine: response.dashbord1_Line
});
if (response.dashbord1_Line && response.dashbord1_Line[0]?.model) {
const modelData = response.dashbord1_Line[0].model;
const parsedModel = JSON.parse(modelData);
if (Array.isArray(parsedModel.dashboard)) {
const dashboardLayout = parsedModel.dashboard.map((widget, index) => {
const componentName = widget.component || "Unknown";
const typeIdentifier = componentName.toLowerCase().replace(' ', '_');
const Component = componentMap[typeIdentifier] || null;
return {
id: `widget-${index}`,
x: widget.x || 0,
y: widget.y || 0,
w: widget.cols || 6,
h: widget.rows || 8,
name: widget.name || componentName,
component: Component,
chartTitle: widget.chartTitle || widget.name || componentName,
showLegend: widget.showLegend !== undefined ? widget.showLegend : true,
showLabel: widget.showLabel !== undefined ? widget.showLabel : true,
xAxis: widget.xAxis || 'Month',
yAxis: widget.yAxis || 'Value'
};
});
setLayout(dashboardLayout);
}
}
} catch (error) {
console.error("Error fetching dashboard:", error);
setError("Failed to load dashboard. Please try again later.");
} finally {
setLoading(false);
}
};
if (id) {
fetchDashboard();
}
}, [id]);
const getWidgetStyle = (widget) => {
const columnWidth = 100 / 6;
const rowHeight = 40;
return {
position: 'absolute',
left: `${widget.x * columnWidth}%`,
top: `${widget.y * rowHeight}px`,
width: `${widget.w * columnWidth}%`,
height: `${widget.h * rowHeight}px`,
};
};
if (loading) {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
backgroundColor: theme.palette.background.default
}}
>
<CircularProgress />
<Typography variant="h6" sx={{ ml: 2 }}>
Loading Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
flexDirection: 'column',
backgroundColor: theme.palette.background.default
}}
>
<Typography variant="h5" color="error" gutterBottom>
Error Loading Dashboard
</Typography>
<Typography variant="body1">{error}</Typography>
<Button
variant="contained"
onClick={() => navigate(-1)}
sx={{ mt: 2 }}
>
Go Back
</Button>
</Box>
);
}
return (
<Box sx={{
padding: 3,
backgroundColor: theme.palette.background.default,
minHeight: '100vh'
}}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 3
}}>
<Typography variant="h4" color="text.primary">
{dashboard?.name || 'Dashboard View'}
</Typography>
<Button
variant="contained"
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)}
>
Back
</Button>
</Box>
{layout.length === 0 ? (
<Card sx={{ textAlign: 'center', p: 5 }}>
<Typography variant="h6">No widgets found in this dashboard.</Typography>
</Card>
) : (
<Box
sx={{
position: 'relative',
minHeight: '800px'
}}
>
{layout.map((widget) => {
const Component = widget.component;
const style = getWidgetStyle(widget);
return (
<Box
key={widget.id}
sx={{
...style,
padding: 1,
boxSizing: 'border-box'
}}
>
<Card sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
boxShadow: theme.shadows[2]
}}>
<CardHeader
title={widget.chartTitle}
titleTypographyProps={{ variant: 'h6' }}
sx={{
backgroundColor: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`
}}
/>
<CardContent sx={{
flex: 1,
display: 'flex',
flexDirection: 'column',
p: 2,
pt: 1,
overflow: 'hidden'
}}>
{Component ? (
<Box sx={{
flexGrow: 1,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Component
chartTitle={widget.chartTitle}
showLegend={widget.showLegend}
showLabel={widget.showLabel}
xAxis={widget.xAxis}
yAxis={widget.yAxis}
width="100%"
height="100%"
/>
</Box>
) : (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%'
}}>
<Typography variant="body2" color="text.secondary">
Widget type "{widget.name}" not available
</Typography>
</Box>
)}
</CardContent>
</Card>
</Box>
);
})}
</Box>
)}
</Box>
);
};
export default ViewDashboard;

View File

@@ -0,0 +1,182 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import moment from "moment";
import { toast, Toaster } from "react-hot-toast";
import {
Box,
Button,
Card,
CardContent,
CardHeader,
CardActions,
Grid,
Typography,
CircularProgress,
Alert,
IconButton,
Modal
} from "@mui/material";
import {
Add as AddIcon,
CalendarToday as CalendarIcon,
AccessTime as TimeIcon,
Apps as AppsIcon,
Delete as DeleteIcon,
Edit as EditIcon
} from "@mui/icons-material";
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
const DashboardRunnerAll = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [selectedDashboard, setSelectedDashboard] = useState(null);
const navigate = useNavigate();
const { id } = useParams();
useEffect(() => {
fetchDashboard();
}, []);
const fetchDashboard = () => {
setLoading(true);
DashboardBuilderService.getAllDash()
.then((response) => {
setData(response);
})
.catch(() => setError("No data available"))
.finally(() => setLoading(false));
};
const handleDelete = (id) => {
setDeleteModalOpen(false);
// Dashboard3Service.deleteField(id).then(() => {
// fetchDashboard();
// toast.success("Deleted successfully");
// });
};
const handleAdd = () => {
navigate("/dashboard/dashboard-new-all");
};
const handleEdit = (id) => {
navigate(`../dashrunner/${id}`);
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
<Typography variant="h4">All Dashboards</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleAdd}
>
Dashboard Builder
</Button>
</Box>
{loading && (
<Box sx={{ display: "flex", justifyContent: "center", my: 4 }}>
<CircularProgress />
</Box>
)}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
)}
<Grid container spacing={3}>
{data.map((app, index) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
cursor: "pointer",
"&:hover": {
boxShadow: 6,
},
}}
onClick={() => handleEdit(app.id)}
>
<CardHeader
avatar={<AppsIcon />}
title={
<Typography noWrap fontWeight="bold">
{app.dashboard_name}
</Typography>
}
/>
<CardContent sx={{ flexGrow: 1 }}>
<Typography variant="body2" color="text.secondary" noWrap>
{app.description || "No description available"}
</Typography>
</CardContent>
<CardActions sx={{ justifyContent: "space-between" }}>
<Typography variant="caption" color="text.secondary">
<TimeIcon fontSize="small" sx={{ verticalAlign: "middle", mr: 0.5 }} />
{moment(app.updatedAt).format("DD/MM/YYYY")}
</Typography>
<Typography variant="caption" color="text.secondary">
<CalendarIcon fontSize="small" sx={{ verticalAlign: "middle", mr: 0.5 }} />
{moment(app.createdAt).format("HH:mm:ss")}
</Typography>
</CardActions>
</Card>
</Grid>
))}
</Grid>
{/* Delete Confirmation Modal */}
<Modal
open={deleteModalOpen}
onClose={() => setDeleteModalOpen(false)}
>
<Box sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
borderRadius: 1
}}>
<Typography variant="h6" gutterBottom>
Delete Confirmation
</Typography>
<Typography variant="body1" sx={{ mb: 3 }}>
Are you sure you want to delete this dashboard?
</Typography>
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 2 }}>
<Button
variant="outlined"
onClick={() => setDeleteModalOpen(false)}
>
Cancel
</Button>
<Button
variant="contained"
color="error"
onClick={() => handleDelete(selectedDashboard?.id)}
>
Delete
</Button>
</Box>
</Box>
</Modal>
<Toaster position="top-right" />
</Box>
);
};
export default DashboardRunnerAll;

View File

@@ -0,0 +1,548 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
Card,
CardContent,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
FormControlLabel,
IconButton,
TextField,
Typography,
useTheme
} from '@mui/material';
import {
DragIndicator as DragIndicatorIcon,
Edit as EditIcon,
Delete as DeleteIcon,
Add as AddIcon,
Cancel as CancelIcon,
Save as SaveIcon,
ArrowBack as ArrowBackIcon
} from '@mui/icons-material';
import { Responsive, WidthProvider } from 'react-grid-layout';
import { useNavigate, useParams } from 'react-router-dom';
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
// Import chart components
import LineChartComponent from '../gadgets/linechart';
import PieChartComponent from '../gadgets/piechart';
import PolarChartComponent from '../gadgets/polarchart';
import RadarChartComponent from '../gadgets/radarchart';
import BubbleChart from '../gadgets/bubblechart';
import BarChart from '../gadgets/barchart';
import DoughnutChart from '../gadgets/doughnut';
import DynamicChart from '../gadgets/dynamicchart';
import FinancialChart from '../gadgets/financialchart';
import GridViewComponent from '../gadgets/gridview';
import ScatterChartComponent from '../gadgets/scatterchart';
import ToDoChartComponent from '../gadgets/todochart';
const ResponsiveGridLayout = WidthProvider(Responsive);
const EditNewDash = () => {
const { id } = useParams();
const theme = useTheme();
const navigate = useNavigate();
const [sidebarOpen, setSidebarOpen] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [currentEditWidget, setCurrentEditWidget] = useState({});
const [dashboardName, setDashboardName] = useState('');
const [dashboardLine, setDashboardLine] = useState([]);
const [dashboardlineId, setDashboardlineId] = useState(null);
const [dashboardWidgets, setDashboardWidgets] = useState([]);
const availableWidgets = [
{ identifier: 'radar_chart', name: 'Radar Chart' },
{ identifier: 'polar_chart', name: 'Polar Chart' },
{ identifier: 'pie_chart', name: 'Pie Chart' },
{ identifier: 'bar_chart', name: 'Bar Chart' },
{ identifier: 'bubble_chart', name: 'Bubble Chart' },
{ identifier: 'line_chart', name: 'Line Chart' },
{ identifier: 'doughnut_chart', name: 'Doughnut Chart' },
{ identifier: 'dynamic_chart', name: 'Dynamic Chart' },
{ identifier: 'financial_chart', name: 'Financial Chart' },
{ identifier: 'grid_view', name: 'Grid View' },
{ identifier: 'scatter_chart', name: 'Scatter Chart' },
{ identifier: 'todo_chart', name: 'Todo Chart' },
];
const componentMap = {
'radar_chart': RadarChartComponent,
'polar_chart': PolarChartComponent,
'pie_chart': PieChartComponent,
'bar_chart': BarChart,
'bubble_chart': BubbleChart,
'line_chart': LineChartComponent,
'doughnut_chart': DoughnutChart,
'dynamic_chart': DynamicChart,
'financial_chart': FinancialChart,
'grid_view': GridViewComponent,
'scatter_chart': ScatterChartComponent,
'todo_chart': ToDoChartComponent
};
const componentNameToIdentifier = {
'RadarChartComponent': 'radar_chart',
'PolarChartComponent': 'polar_chart',
'PieChartComponent': 'pie_chart',
'BarChart': 'bar_chart',
'BubbleChart': 'bubble_chart',
'LineChartComponent': 'line_chart',
'DoughnutChart': 'doughnut_chart',
'DynamicChart': 'dynamic_chart',
'FinancialChart': 'financial_chart',
'GridViewComponent': 'grid_view',
'ScatterChartComponent': 'scatter_chart',
'ToDoChartComponent': 'todo_chart'
};
useEffect(() => {
fetchDashboardData();
}, []);
const fetchDashboardData = async () => {
try {
const response = await DashboardBuilderService.getById(id);
setDashboardlineId(response.dashbord1_Line[0].id);
setDashboardName(response.dashboard_name);
setDashboardLine(response.dashbord1_Line);
if (response.dashbord1_Line && response.dashbord1_Line[0]?.model) {
const modelData = response.dashbord1_Line[0].model;
const parsedModel = JSON.parse(modelData);
const transformedWidgets = Array.isArray(parsedModel.dashboard)
? parsedModel.dashboard.map((widget, index) => {
const widgetId = widget.i || `widget-${index}`;
const typeIdentifier = widget.type ||
(widget.component && typeof widget.component === 'string'
? componentNameToIdentifier[widget.component] || widget.component.toLowerCase().replace(' ', '_')
: 'unknown');
const componentRef = componentMap[typeIdentifier] || null;
return {
i: widgetId,
x: widget.x || 0,
y: widget.y || 0,
w: widget.cols || 6,
h: widget.rows || 8,
type: typeIdentifier,
name: widget.name || availableWidgets.find(w => w.identifier === typeIdentifier)?.name || 'Unknown Widget',
component: componentRef,
chartTitle: widget.chartTitle,
showLegend: widget.showLegend,
showLabel: widget.showLabel,
xAxis: widget.xAxis,
yAxis: widget.yAxis
};
})
: [];
setDashboardWidgets(transformedWidgets);
}
} catch (error) {
console.error("Error fetching dashboard data:", error);
}
};
const onDragStart = (e, widget) => {
e.dataTransfer.setData('widgetType', widget.identifier);
};
const onDragOver = (e) => {
e.preventDefault();
};
const onDrop = (e) => {
e.preventDefault();
const widgetType = e.dataTransfer.getData('widgetType');
if (!widgetType) return;
const gridRect = e.currentTarget.getBoundingClientRect();
const x = Math.floor((e.clientX - gridRect.left) / 100) * 2;
const y = Math.floor((e.clientY - gridRect.top) / 100) * 2;
const newWidgetId = `widget-${Date.now()}`;
const componentRef = componentMap[widgetType] || null;
const widgetName = availableWidgets.find(w => w.identifier === widgetType)?.name || 'Unknown Widget';
const newWidget = {
i: newWidgetId,
x: x,
y: y,
w: 6,
h: 8,
type: widgetType,
name: widgetName,
component: componentRef,
chartTitle: widgetName,
showLegend: true,
showLabel: true,
xAxis: 'Month',
yAxis: 'Value'
};
setDashboardWidgets(prev => [...prev, newWidget]);
};
const onLayoutChange = (layout) => {
const updatedWidgets = dashboardWidgets.map(widget => {
const updatedPosition = layout.find(item => item.i === widget.i);
if (updatedPosition) {
return { ...widget, x: updatedPosition.x, y: updatedPosition.y, w: updatedPosition.w, h: updatedPosition.h };
}
return widget;
});
setDashboardWidgets(updatedWidgets);
};
const removeWidget = (widgetId) => {
setDashboardWidgets(prev => prev.filter(widget => widget.i !== widgetId));
};
const editWidget = (widget) => {
setCurrentEditWidget(widget);
setEditModalOpen(true);
};
const saveWidgetChanges = () => {
setDashboardWidgets(prev =>
prev.map(widget =>
widget.i === currentEditWidget.i ? { ...widget, ...currentEditWidget } : widget
)
);
setEditModalOpen(false);
};
const goBack = () => {
navigate('/dashboard/dashboard-new-all');
};
const saveDashboard = async () => {
try {
if (!dashboardWidgets || dashboardWidgets.length === 0) {
console.warn("No widgets to save.");
return;
}
const dashboardData = {
dashboard: dashboardWidgets.map(widget => ({
i: widget.i,
x: widget.x ?? 0,
y: widget.y ?? 0,
w: widget.w ?? 6,
h: widget.h ?? 8,
type: widget.type,
name: widget.name || "Unknown",
chartTitle: widget.chartTitle,
showLegend: widget.showLegend,
showLabel: widget.showLabel,
xAxis: widget.xAxis,
yAxis: widget.yAxis
})),
};
const dashbord1_Line = {
model: JSON.stringify(dashboardData)
};
const response = await DashboardBuilderService.updateLineData(dashboardlineId, dashbord1_Line);
if (response) {
navigate('/dashboard/dashboard-new-all');
}
} catch (error) {
console.error("Error saving dashboard:", error);
}
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
height: '100vh',
padding: 3,
backgroundColor: theme.palette.background.default
}}>
{/* Toolbar */}
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
marginBottom: 3
}}>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setSidebarOpen(!sidebarOpen)}
sx={{ mr: 2 }}
>
{sidebarOpen ? 'Hide Components' : 'Add Components'}
</Button>
<Typography variant="h5" color="text.primary">
{dashboardName}
</Typography>
<Box>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={goBack}
sx={{ mr: 2 }}
>
Cancel
</Button>
<Button
variant="contained"
color="primary"
startIcon={<SaveIcon />}
onClick={saveDashboard}
>
Save Dashboard
</Button>
</Box>
</Box>
{/* Main content */}
<Box sx={{
display: 'flex',
flex: 1,
width: '100%',
overflow: 'hidden'
}}>
{/* Sidebar */}
{sidebarOpen && (
<Box sx={{
width: 250,
backgroundColor: theme.palette.background.paper,
padding: 2,
overflowY: 'auto',
borderRight: `1px solid ${theme.palette.divider}`,
boxShadow: theme.shadows[2]
}}>
<Typography variant="h6" sx={{ marginBottom: 2 }}>
Available Components
</Typography>
<Box sx={{
display: 'grid',
gap: 1
}}>
{availableWidgets.map(widget => (
<Card
key={widget.identifier}
sx={{
cursor: 'move',
'&:hover': {
backgroundColor: theme.palette.action.hover
}
}}
draggable
onDragStart={(e) => onDragStart(e, widget)}
>
<CardContent sx={{
display: 'flex',
alignItems: 'center',
padding: '8px !important'
}}>
<DragIndicatorIcon sx={{ mr: 1, color: theme.palette.text.secondary }} />
<Typography variant="body2">{widget.name}</Typography>
</CardContent>
</Card>
))}
</Box>
</Box>
)}
{/* Dashboard grid */}
<Box
sx={{
flex: 1,
padding: 2,
overflow: 'auto',
backgroundColor: theme.palette.background.default
}}
onDragOver={onDragOver}
onDrop={onDrop}
>
<ResponsiveGridLayout
className="layout"
draggableCancel=".MuiButton-root, .MuiIconButton-root, button, [role='button']"
layouts={{
lg: dashboardWidgets.map(widget => ({
i: widget.i,
x: widget.x,
y: widget.y,
w: widget.w,
h: widget.h,
minW: 3,
minH: 3
}))
}}
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
rowHeight={30}
onLayoutChange={onLayoutChange}
isDraggable
isResizable
margin={[20, 20]}
>
{dashboardWidgets.map(widget => (
<Box key={widget.i} sx={{ overflow: 'hidden' }}>
<Card sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
boxShadow: theme.shadows[2]
}}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 1,
borderBottom: `1px solid ${theme.palette.divider}`,
backgroundColor: theme.palette.background.paper
}}>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
{widget.name}
</Typography>
<Box>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
editWidget(widget);
}}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
size="small"
color="error"
onClick={(e) => {
e.stopPropagation();
removeWidget(widget.i);
}}
sx={{ ml: 1 }}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Box>
</Box>
<CardContent sx={{
flex: 1,
padding: 2,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
height: 'calc(100% - 50px)'
}}>
{widget.component && (
<Box sx={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden'
}}>
<widget.component
chartTitle={widget.chartTitle || widget.name}
showLegend={widget.showLegend !== undefined ? widget.showLegend : true}
showLabel={widget.showLabel !== undefined ? widget.showLabel : true}
xAxis={widget.xAxis || 'Month'}
yAxis={widget.yAxis || 'Value'}
width="100%"
height="100%"
/>
</Box>
)}
</CardContent>
</Card>
</Box>
))}
</ResponsiveGridLayout>
</Box>
</Box>
{/* Edit widget dialog */}
<Dialog open={editModalOpen} onClose={() => setEditModalOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>Edit {currentEditWidget?.name}</DialogTitle>
<DialogContent dividers>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
<TextField
label="Chart Title"
fullWidth
value={currentEditWidget?.chartTitle || ''}
onChange={(e) =>
setCurrentEditWidget((prev) => ({ ...prev, chartTitle: e.target.value }))
}
/>
<TextField
label="X Axis Label"
fullWidth
value={currentEditWidget?.xAxis || ''}
onChange={(e) =>
setCurrentEditWidget((prev) => ({ ...prev, xAxis: e.target.value }))
}
/>
<TextField
label="Y Axis Label"
fullWidth
value={currentEditWidget?.yAxis || ''}
onChange={(e) =>
setCurrentEditWidget((prev) => ({ ...prev, yAxis: e.target.value }))
}
/>
<FormControl>
<FormControlLabel
control={
<Checkbox
checked={currentEditWidget?.showLegend ?? true}
onChange={(e) =>
setCurrentEditWidget((prev) => ({ ...prev, showLegend: e.target.checked }))
}
/>
}
label="Show Legend"
/>
<FormControlLabel
control={
<Checkbox
checked={currentEditWidget?.showLabel ?? true}
onChange={(e) =>
setCurrentEditWidget((prev) => ({ ...prev, showLabel: e.target.checked }))
}
/>
}
label="Show Labels"
/>
</FormControl>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setEditModalOpen(false)} startIcon={<CancelIcon />}>
Cancel
</Button>
<Button
onClick={saveWidgetChanges}
variant="contained"
color="primary"
startIcon={<SaveIcon />}
>
Save Changes
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default EditNewDash;

View File

@@ -0,0 +1,162 @@
import React, { useState, useEffect } from 'react';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import {
Box,
Button,
Card,
Checkbox,
FormControl,
FormControlLabel,
TextField,
Typography,
Chip
} from '@mui/material';
import {
Save as SaveIcon,
ArrowBack as ArrowBackIcon
} from '@mui/icons-material';
import DashboardBuilderService from "../../../../src/ApiServices/DashboardBuilderServices";
const EditFormNewDash = () => {
const { id } = useParams();
const navigate = useNavigate();
const location = useLocation();
const [header, setHeader] = useState({
dashboard_name: '',
secuirity_profile: '',
description: '',
add_to_home: false,
});
useEffect(() => {
if (location.state?.dashboard) {
setHeader(location.state.dashboard);
} else {
getById(id);
}
}, [id, location.state]);
const getById = (id) => {
DashboardBuilderService.getById(id)
.then((data) => {
setHeader(data);
})
.catch((err) => {
console.error("Error fetching dashboard by ID:", err);
toast.error('Failed to fetch dashboard data.');
});
};
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setHeader((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}));
};
const update = async () => {
try {
const updatedDashboard = await DashboardBuilderService.updateDash(header);
if (updatedDashboard) {
toast.success('Dashboard updated successfully');
navigate('/dashboard/dashboard-new-all');
}
} catch (error) {
console.error("Error updating dashboard:", error);
toast.error('Failed to update dashboard.');
}
};
const onSubmit = (e) => {
e.preventDefault();
update();
};
const onBack = () => {
navigate('/dashboard/dashboard-new-all');
};
return (
<Card sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
<Typography variant="h5">Dashboard</Typography>
<Chip
label="Edit Mode"
color="info"
size="small"
sx={{ ml: 2 }}
/>
</Box>
<Box component="form" onSubmit={onSubmit}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 3, mb: 3 }}>
<FormControl fullWidth>
<TextField
label="Dashboard Name"
name="dashboard_name"
value={header.dashboard_name || ''}
onChange={handleInputChange}
required
/>
</FormControl>
<FormControl fullWidth>
<TextField
label="Security Profile"
name="secuirity_profile"
value={header.secuirity_profile || ''}
onChange={handleInputChange}
required
/>
</FormControl>
<FormControl fullWidth sx={{ gridColumn: '1 / -1' }}>
<TextField
label="Description"
name="description"
value={header.description || ''}
onChange={handleInputChange}
multiline
rows={3}
required
/>
</FormControl>
<FormControlLabel
control={
<Checkbox
name="add_to_home"
checked={header.add_to_home || false}
onChange={handleInputChange}
color="primary"
/>
}
label="Add to Dashboard"
/>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={onBack}
>
Back
</Button>
<Button
type="submit"
variant="contained"
color="primary"
startIcon={<SaveIcon />}
>
Update
</Button>
</Box>
</Box>
</Card>
);
};
export default EditFormNewDash;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { Box } from '@mui/material';
import { Bar } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const BarChart = () => {
const data = {
labels: ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'],
datasets: [
{
label: 'Best Fruits',
data: [45, 37, 60, 70, 46, 33],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Bar Chart',
},
},
};
return (
<Box sx={{ height: 400 }}>
<Bar data={data} options={options} />
</Box>
);
};
export default BarChart;

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { Box } from '@mui/material';
import { Bubble } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const BubbleChart = () => {
const data = {
datasets: [
{
data: [
{ x: 10, y: 10, r: 10 },
{ x: 15, y: 5, r: 15 },
{ x: 26, y: 12, r: 23 },
{ x: 7, y: 8, r: 8 },
],
label: 'Investment Equities',
backgroundColor: 'rgba(255, 0, 0, 0.6)',
borderColor: 'blue',
},
{
data: [
{ x: 5, y: 15, r: 12 },
{ x: 20, y: 7, r: 8 },
{ x: 12, y: 18, r: 15 },
{ x: 8, y: 6, r: 10 },
],
label: 'Investment Bonds',
backgroundColor: 'rgba(0, 255, 0, 0.6)',
borderColor: 'green',
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Bubble Chart',
},
},
scales: {
x: { min: 0, max: 30 },
y: { min: 0, max: 30 },
},
};
return (
<Box sx={{ height: 400 }}>
<Bubble data={data} options={options} />
</Box>
);
};
export default BubbleChart;

View File

@@ -0,0 +1,28 @@
// chartUtils.js
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
ArcElement,
RadialLinearScale,
Title,
Tooltip,
Legend,
} from '../../../variables/chart';
// chartutils.js
import { Chart } from 'chart.js';
import { registerables } from 'chart.js';
export const registerChartJS = () => {
Chart.register(...registerables);
};
export const commonChartOptions = {
responsive: true,
maintainAspectRatio: false,
};

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Box } from '@mui/material';
import { Doughnut } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const DoughnutChart = () => {
const data = {
labels: ["Download Sales", "In-Store Sales", "Mail-Order Sales"],
datasets: [
{
data: [350, 450, 100],
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)'
],
borderWidth: 1,
},
],
};
const options = {
...commonChartOptions,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Doughnut Chart',
},
},
};
return (
<Box sx={{ height: 400 }}>
<Doughnut data={data} options={options} />
</Box>
);
};
export default DoughnutChart;

View File

@@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { Box, Button } from '@mui/material';
import { Bar } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const DynamicChart = () => {
const [chartType, setChartType] = useState('bar');
const labels = ['2006', '2007', '2008', '2009', '2010', '2011', '2012'];
const data = {
labels,
datasets: [
{
data: [65, 59, 90, 81, 56, 55, 40],
label: 'Series A',
backgroundColor: 'rgba(75, 192, 192, 0.6)'
},
{
data: [28, 48, 40, 19, 96, 27, 100],
label: 'Series B',
backgroundColor: 'rgba(153, 102, 255, 0.6)'
},
],
};
const options = {
...commonChartOptions,
plugins: {
legend: {
display: true,
},
title: {
display: true,
text: 'Dynamic Chart',
},
},
};
const randomize = () => {
setChartType(prev => prev === 'bar' ? 'line' : 'bar');
};
return (
<Box sx={{ height: 500 }}>
<Bar
data={data}
options={options}
type={chartType}
/>
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Button variant="contained" onClick={randomize}>
Toggle Chart Type
</Button>
</Box>
</Box>
);
};
export default DynamicChart;

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { Box } from '@mui/material';
import { Line } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const FinancialChart = ({ data }) => {
const defaultData = [
{ date: '2024-03-01', price: 120 },
{ date: '2024-03-02', price: 125 },
{ date: '2024-03-03', price: 130 },
{ date: '2024-03-04', price: 128 },
{ date: '2024-03-05', price: 135 },
];
const stockData = data?.length > 0 ? data : defaultData;
const chartData = {
labels: stockData.map(item => item.date),
datasets: [
{
label: 'Stock Price',
data: stockData.map(item => item.price),
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true,
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Financial Data Chart',
},
tooltip: {
callbacks: {
label: (tooltipItem) => `$${tooltipItem.raw.toFixed(2)}`,
},
},
},
scales: {
x: {
title: { display: true, text: 'Date' },
},
y: {
title: { display: true, text: 'Price (USD)' },
ticks: { callback: (value) => `$${value.toFixed(2)}` },
},
},
};
return (
<Box sx={{ height: 400 }}>
<Line data={chartData} options={options} />
</Box>
);
};
export default FinancialChart;

View File

@@ -0,0 +1,107 @@
import React, { useState } from 'react';
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Pagination,
CircularProgress,
Typography
} from '@mui/material';
const GridViewComponent = () => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const [selectedRow, setSelectedRow] = useState(null);
// Simulated data - replace with actual API call
const mockData = [
{ usrGrp: 1, groupName: 'Admin', groupDesc: 'Administrators', groupLevel: 1, status: 'Active', updateDateFormated: '2023-01-01' },
{ usrGrp: 2, groupName: 'Users', groupDesc: 'Regular users', groupLevel: 2, status: 'Active', updateDateFormated: '2023-01-02' },
{ usrGrp: 3, groupName: 'Guests', groupDesc: 'Guest access', groupLevel: 3, status: 'Inactive', updateDateFormated: '2023-01-03' },
];
// Simulate data loading
React.useEffect(() => {
setLoading(true);
setTimeout(() => {
setData(mockData);
setLoading(false);
}, 1000);
}, []);
const handlePageChange = (event, value) => {
setPage(value);
};
const handleRowClick = (row) => {
setSelectedRow(row);
};
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
User Group Maintenance
</Typography>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
<CircularProgress />
</Box>
) : error ? (
<Typography color="error">{error}</Typography>
) : (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>User Group No</TableCell>
<TableCell>Group Name</TableCell>
<TableCell>Description</TableCell>
<TableCell>Group Level</TableCell>
<TableCell>Status</TableCell>
<TableCell>Updated Date</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((row) => (
<TableRow
key={row.usrGrp}
hover
selected={selectedRow?.usrGrp === row.usrGrp}
onClick={() => handleRowClick(row)}
>
<TableCell>{row.usrGrp}</TableCell>
<TableCell>{row.groupName}</TableCell>
<TableCell>{row.groupDesc}</TableCell>
<TableCell>{row.groupLevel}</TableCell>
<TableCell>{row.status}</TableCell>
<TableCell>{row.updateDateFormated}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Pagination
count={3}
page={page}
onChange={handlePageChange}
color="primary"
/>
</Box>
</>
)}
</Box>
);
};
export default GridViewComponent;

View File

@@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { Box, Button } from '@mui/material';
import { Line } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const LineChartComponent = ({
chartTitle = 'Line Chart',
showLegend = true,
showLabel = true,
xAxis = 'Category',
yAxis = 'Value'
}) => {
const [data, setData] = useState({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'Series A',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
},
{
label: 'Series B',
data: [28, 48, 40, 19, 86, 27, 90],
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
tension: 0.1
},
]
});
const options = {
...commonChartOptions,
plugins: {
legend: { display: showLegend },
title: { display: true, text: chartTitle },
},
scales: {
x: { title: { display: showLabel, text: xAxis } },
y: { title: { display: showLabel, text: yAxis } }
}
};
const randomize = () => {
setData(prev => ({
...prev,
datasets: prev.datasets.map(dataset => ({
...dataset,
data: Array.from({ length: 7 }, () => Math.floor(Math.random() * 100) + 1)
}))
}));
};
return (
<Box sx={{ height: 400 }}>
<Line data={data} options={options} />
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
<Button variant="outlined" onClick={randomize}>
Randomize Data
</Button>
</Box>
</Box>
);
};
export default LineChartComponent;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { Box } from '@mui/material';
import { Pie } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const PieChartComponent = () => {
const data = {
labels: ['SciFi', 'Drama', 'Comedy'],
datasets: [
{
data: [30, 50, 20],
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'],
borderWidth: 1,
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Pie Chart',
},
},
};
return (
<Box sx={{ height: 400 }}>
<Pie data={data} options={options} />
</Box>
);
};
export default PieChartComponent;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Box } from '@mui/material';
import { PolarArea } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const PolarChartComponent = () => {
const data = {
labels: ['Download Sales', 'In-Store Sales', 'Mail Sales', 'Telesales', 'Corporate Sales'],
datasets: [
{
data: [300, 500, 100, 40, 120],
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
'rgba(75, 192, 192, 0.6)',
'rgba(153, 102, 255, 0.6)',
],
borderWidth: 1,
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Polar Area Chart',
},
},
};
return (
<Box sx={{ height: 400 }}>
<PolarArea data={data} options={options} />
</Box>
);
};
export default PolarChartComponent;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Box } from '@mui/material';
import { Radar } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const RadarChartComponent = () => {
const data = {
labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'],
datasets: [
{
label: 'Series A',
data: [65, 59, 90, 81, 56, 55, 40],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
},
{
label: 'Series B',
data: [28, 48, 40, 19, 96, 27, 100],
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Radar Chart',
},
},
};
return (
<Box sx={{ height: 400 }}>
<Radar data={data} options={options} />
</Box>
);
};
export default RadarChartComponent;

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { Box } from '@mui/material';
import { Scatter } from 'react-chartjs-2';
import { registerChartJS, commonChartOptions } from './chartutils';
registerChartJS();
const ScatterChartComponent = () => {
const data = {
datasets: [
{
label: 'Scatter Dataset',
data: [
{ x: 1, y: 1 },
{ x: 2, y: 3 },
{ x: 3, y: -2 },
{ x: 4, y: 4 },
{ x: 5, y: -3 },
],
backgroundColor: 'rgba(255, 99, 132, 0.6)',
},
],
};
const options = {
...commonChartOptions,
plugins: {
title: {
display: true,
text: 'Scatter Chart',
},
},
scales: {
x: { type: 'linear', position: 'bottom' },
},
};
return (
<Box sx={{ height: 400 }}>
<Scatter data={data} options={options} />
</Box>
);
};
export default ScatterChartComponent;

View File

@@ -0,0 +1,93 @@
import React, { useState } from 'react';
const ToDoChartComponent = () => {
const [todo, setTodo] = useState('');
const [todoList, setTodoList] = useState(['todo 1']);
const addTodo = (newTodo) => {
if (newTodo.trim()) {
setTodoList([...todoList, newTodo]);
setTodo('');
}
};
const removeTodo = (index) => {
// Don't allow removing the first item ('todo 1')
if (index === 0) return;
const updatedTodoList = todoList.filter((_, i) => i !== index);
setTodoList(updatedTodoList);
};
return (
<div style={{ padding: '15px' }}>
<table className="table" style={{ width: '100%' }}>
<thead>
<tr>
<th style={{ width: '10%' }}>#</th>
<th style={{ width: '70%' }}>Item</th>
<th style={{ width: '20%', textAlign: 'right' }}>Action</th>
</tr>
</thead>
<tbody>
{todoList.map((todoItem, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{todoItem}</td>
<td style={{ textAlign: 'right' }}>
{index !== 0 && ( // Don't show delete button for the first item
<button
onClick={() => removeTodo(index)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
color: '#ff4444'
}}
title="Remove item"
>
×
</button>
)}
</td>
</tr>
))}
<tr>
<td></td>
<td>
<input
value={todo}
onChange={(e) => setTodo(e.target.value)}
placeholder="Add new todo"
style={{
width: '100%',
padding: '8px',
border: '1px solid #ddd',
borderRadius: '4px'
}}
/>
</td>
<td style={{ textAlign: 'right' }}>
<button
onClick={() => addTodo(todo)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
color: '#4CAF50',
fontSize: '1.2em'
}}
title="Add item"
disabled={!todo.trim()}
>
+
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
};
export default ToDoChartComponent;

View File

@@ -0,0 +1,61 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { getSysParameter } from '../ApiServices/SytemparameterApi';
const SystemParameterContext = createContext();
export const useSystemParameters = () => {
const context = useContext(SystemParameterContext);
if (!context) {
throw new Error('useSystemParameters must be used within a SystemParameterProvider');
}
return context;
};
export const SystemParameterProvider = ({ children }) => {
const [systemParameters, setSystemParameters] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchSystemParameters = async () => {
try {
const data = await getSysParameter(1); // Assuming 1 is the default ID for system parameters
setSystemParameters(data);
console.log("system parameters data ", data);
setError(null);
} catch (err) {
setError('Failed to fetch system parameters');
console.error('Error fetching system parameters:', err);
} finally {
setLoading(false);
}
};
fetchSystemParameters();
}, []);
const value = {
systemParameters,
loading,
error,
refetch: async () => {
setLoading(true);
try {
const data = await getSysParameter(1);
setSystemParameters(data);
setError(null);
} catch (err) {
setError('Failed to fetch system parameters');
console.error('Error fetching system parameters:', err);
} finally {
setLoading(false);
}
}
};
return (
<SystemParameterContext.Provider value={value}>
{children}
</SystemParameterContext.Provider>
);
};

View File

@@ -17,5 +17,3 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -1,22 +1,12 @@
// index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import App from './App';
import './index.css';
import ErrorBoundary from './ErrorBoundary';
const theme = createTheme({
// your theme configuration
});
import { createRoot } from 'react-dom/client';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<ErrorBoundary>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</ErrorBoundary>
);
root.render(<App />);

View File

@@ -1,19 +1,23 @@
const TOKEN_KEY = 'authToken';
const TOKEN_KEY = 'CurrentUser';
export const setToken = (token) => {
localStorage.setItem(TOKEN_KEY, token);
export const setCurrentUser = (user) => {
localStorage.setItem(TOKEN_KEY, user);
};
export const getCurrentUser = () => {
return JSON.parse(localStorage.getItem(TOKEN_KEY));
};
export const getToken = () => {
return localStorage.getItem(TOKEN_KEY);
const userData = localStorage.getItem(TOKEN_KEY);
return userData ? JSON.parse(userData).token : null;
};
export const removeToken = () => {
export const removeCurrentUser = () => {
localStorage.removeItem(TOKEN_KEY);
};
@@ -21,3 +25,7 @@ export const removeToken = () => {
export const isTokenAvailable = () => {
return !!localStorage.getItem(TOKEN_KEY); // Returns true if token exists
};

233
src/variables/chart.js Normal file
View File

@@ -0,0 +1,233 @@
/*!
=========================================================
* Argon Dashboard React - v1.2.4
=========================================================
* Product Page: https://www.creative-tim.com/product/argon-dashboard-react
* Copyright 2024 Creative Tim (https://www.creative-tim.com)
* Licensed under MIT (https://github.com/creativetimofficial/argon-dashboard-react/blob/master/LICENSE.md)
* Coded by Creative Tim
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// =========================================================
// Chart.js v3+ compatible version - UPDATED
// =========================================================
// Colors
const colors = {
gray: {
100: "#f6f9fc",
200: "#e9ecef",
300: "#dee2e6",
400: "#ced4da",
500: "#adb5bd",
600: "#8898aa",
700: "#525f7f",
800: "#32325d",
900: "#212529",
},
theme: {
default: "#172b4d",
primary: "#5e72e4",
secondary: "#f4f5f7",
info: "#11cdef",
success: "#2dce89",
danger: "#f5365c",
warning: "#fb6340",
},
black: "#12263F",
white: "#FFFFFF",
transparent: "transparent",
};
// Methods
// Chart.js global options
function chartOptions() {
const mode = "light";
return {
plugins: {
legend: {
display: false,
},
tooltip: {
mode: "index",
intersect: false,
}
},
responsive: true,
maintainAspectRatio: false,
scales: {
// New scale configuration format for Chart.js v3+
y: {
grid: {
borderDash: [2],
borderDashOffset: [2],
color: mode === "dark" ? colors.gray[900] : colors.gray[300],
drawBorder: false,
drawTicks: false,
zeroLineColor: mode === "dark" ? colors.gray[900] : colors.gray[300],
zeroLineBorderDash: [2],
zeroLineBorderDashOffset: [2],
},
beginAtZero: true,
ticks: {
padding: 10,
callback: function (value) {
if (!(value % 10)) {
return value;
}
},
}
},
x: {
grid: {
drawBorder: false,
drawOnChartArea: false,
drawTicks: false,
},
ticks: {
padding: 20,
}
}
}
};
}
// Parse global options
function parseOptions(Chart, options) {
// Update defaults for Chart.js v3+
Chart.defaults.color = colors.gray[600];
Chart.defaults.font.family = 'Open Sans';
Chart.defaults.font.size = 13;
// Apply any additional options
if (options) {
for (var item in options) {
if (typeof options[item] !== "object") {
Chart.defaults[item] = options[item];
} else {
parseOptions(Chart.defaults[item], options[item]);
}
}
}
}
// Example 1 of Chart inside src/views/Index.js (Sales value - Card)
const chartExample1 = {
options: {
scales: {
y: {
grid: {
color: colors.gray[900],
zeroLineColor: colors.gray[900],
},
ticks: {
callback: function (value) {
if (!(value % 10)) {
return "$" + value + "k";
}
},
},
},
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
var label = context.dataset.label || "";
var yLabel = context.parsed.y;
var content = "";
if (context.chart.data.datasets.length > 1) {
content += label;
}
content += "$" + yLabel + "k";
return content;
},
},
},
},
},
data1: {
labels: ["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
datasets: [
{
label: "Performance",
data: [0, 20, 10, 30, 15, 40, 20, 60, 60],
borderColor: colors.theme.primary,
backgroundColor: "rgba(94, 114, 228, 0.1)",
tension: 0.4,
fill: true,
},
],
},
data2: {
labels: ["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
datasets: [
{
label: "Performance",
data: [0, 20, 5, 25, 10, 30, 15, 40, 40],
borderColor: colors.theme.primary,
backgroundColor: "rgba(94, 114, 228, 0.1)",
tension: 0.4,
fill: true,
},
],
},
};
// Example 2 of Chart inside src/views/Index.js (Total orders - Card)
const chartExample2 = {
options: {
scales: {
y: {
ticks: {
callback: function (value) {
if (!(value % 10)) {
return value;
}
},
},
},
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
var label = context.dataset.label || "";
var yLabel = context.parsed.y;
var content = "";
if (context.chart.data.datasets.length > 1) {
content += label;
}
content += yLabel;
return content;
},
},
},
},
},
data: {
labels: ["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
datasets: [
{
label: "Sales",
data: [25, 20, 30, 22, 17, 29],
backgroundColor: colors.theme.warning,
borderRadius: 6,
maxBarThickness: 10,
},
],
},
};
module.exports = {
chartOptions, // used inside src/views/Index.js
parseOptions, // used inside src/views/Index.js
chartExample1, // used inside src/views/Index.js
chartExample2, // used inside src/views/Index.js
};