base_project
This commit is contained in:
@@ -0,0 +1 @@
|
||||
REACT_APP_API_BASE_URL = http://157.66.191.31:33730/back
|
||||
1
testreactdatatype-demot1-f/authsec_react_materail_ui/.gitignore
vendored
Normal file
1
testreactdatatype-demot1-f/authsec_react_materail_ui/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
21885
testreactdatatype-demot1-f/authsec_react_materail_ui/package-lock.json
generated
Normal file
21885
testreactdatatype-demot1-f/authsec_react_materail_ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"name": "log",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@mui/icons-material": "^5.17.1",
|
||||
"@mui/material": "^5.17.1",
|
||||
"@mui/styles": "^5.16.4",
|
||||
"@mui/x-charts": "^7.6.2",
|
||||
"@mui/x-data-grid": "^7.6.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@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",
|
||||
"chart.js": "^4.4.9",
|
||||
"file-saver": "^2.0.5",
|
||||
"mdb-react-ui-kit": "^7.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-barcode": "^1.5.3",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-data-grid": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"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",
|
||||
"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": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
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" /> -->
|
||||
<!--
|
||||
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/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
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>CloudnSure</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,160 @@
|
||||
// import axios from 'axios';
|
||||
|
||||
// // Use the same environment variable name consistently
|
||||
// const BASE_URL = process.env.REACT_APP_API_BASE_URL;
|
||||
// console.log('API Base URL:', BASE_URL);
|
||||
// // Enhanced token service
|
||||
// export const getToken = () => {
|
||||
// // Check multiple possible storage locations
|
||||
// return localStorage.getItem('token') ||
|
||||
// sessionStorage.getItem('token') ||
|
||||
// document.cookie.split('; ').find(row => row.startsWith('token='))?.split('=')[1];
|
||||
// };
|
||||
|
||||
// const apiClient = axios.create({
|
||||
// baseURL: BASE_URL,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// });
|
||||
|
||||
// // Enhanced request interceptor
|
||||
// apiClient.interceptors.request.use(
|
||||
// (config) => {
|
||||
// const token = getToken();
|
||||
// if (token) {
|
||||
// config.headers['Authorization'] = `Bearer ${token}`;
|
||||
// // Add debug logging
|
||||
// console.debug('[API] Request with token:', {
|
||||
// url: config.url,
|
||||
// headers: config.headers
|
||||
// });
|
||||
// } else {
|
||||
// console.warn('[API] No token available for request:', config.url);
|
||||
// }
|
||||
// return config;
|
||||
// },
|
||||
// (error) => {
|
||||
// console.error('[API] Request interceptor error:', error);
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// );
|
||||
|
||||
// // Enhanced response interceptor
|
||||
// apiClient.interceptors.response.use(
|
||||
// (response) => {
|
||||
// console.debug('[API] Successful response:', {
|
||||
// url: response.config.url,
|
||||
// status: response.status,
|
||||
// data: response.data
|
||||
// });
|
||||
// return response;
|
||||
// },
|
||||
// (error) => {
|
||||
// const originalRequest = error.config;
|
||||
|
||||
// // Debug logging
|
||||
// console.error('[API] Error response:', {
|
||||
// url: originalRequest.url,
|
||||
// status: error.response?.status,
|
||||
// data: error.response?.data
|
||||
// });
|
||||
|
||||
// // Handle 401 specifically
|
||||
// if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
// originalRequest._retry = true;
|
||||
// console.warn('[API] Attempting token refresh...');
|
||||
// // Add your token refresh logic here if needed
|
||||
// }
|
||||
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// );
|
||||
|
||||
// // API methods
|
||||
// const apiService = {
|
||||
// get: (url, params, options = {}) =>
|
||||
// apiClient.get(url, { ...options, params }).catch(handleError),
|
||||
|
||||
// post: (url, body = {}, options = {}) =>
|
||||
// apiClient.post(url, body, options).catch(handleError),
|
||||
|
||||
// put: (url, body = {}, options = {}) =>
|
||||
// apiClient.put(url, body, options).catch(handleError),
|
||||
|
||||
// delete: (url, options = {}) =>
|
||||
// apiClient.delete(url, options).catch(handleError),
|
||||
|
||||
// // Add patch if needed
|
||||
// patch: (url, body = {}, options = {}) =>
|
||||
// apiClient.patch(url, body, options).catch(handleError)
|
||||
// };
|
||||
|
||||
// // Enhanced error handler
|
||||
// const handleError = (error) => {
|
||||
// // Your existing error handling logic
|
||||
// // ...
|
||||
// };
|
||||
|
||||
// // Specific API endpoints
|
||||
// export const getSubmenuItems = async (id) => {
|
||||
// try {
|
||||
// const response = await apiService.get(`/api1/submenu1/${id}`);
|
||||
// return response.data;
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching submenu items:', error);
|
||||
// throw error; // Re-throw for component-level handling
|
||||
// }
|
||||
// };
|
||||
|
||||
// // Export the configured service
|
||||
// export default apiService;
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = process.env.REACT_APP_API_BASE_URL ;
|
||||
|
||||
const apiService = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Request interceptor for auth token
|
||||
apiService.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response interceptor
|
||||
apiService.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('authToken');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Add downloadFile method
|
||||
apiService.downloadFile = async (url, data) => {
|
||||
try {
|
||||
const response = await apiService.post(url, data, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default apiService;
|
||||
@@ -0,0 +1,22 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const addSQLReport = async (formData) => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const response = await axios.post(
|
||||
`${process.env.REACT_APP_API_BASE_URL}/Rpt_builder2/Rpt_builder2`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error adding SQL report:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
@@ -0,0 +1,17 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
|
||||
export const deleteReportSQL = async (id) => {
|
||||
if (!id) {
|
||||
throw new Error("ID is required for deletion.");
|
||||
}
|
||||
const BASE_URL = `${process.env.REACT_APP_API_BASE_URL}/Rpt_builder2/Rpt_builder2/${id}`;
|
||||
try {
|
||||
const response = await apiService.delete(BASE_URL);
|
||||
console.log("Delete response:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error in API call:", error.response || error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
const API_BASE_URL = 'api';
|
||||
|
||||
const ReportBuilderService = {
|
||||
getById: (id) => apiService.get(`${API_BASE_URL}/edit-report/${id}`),
|
||||
|
||||
buildReport: (id) => {
|
||||
const params = { id };
|
||||
return apiService.get(`${API_BASE_URL}/build_report`, params);
|
||||
},
|
||||
|
||||
getAll: (moduleId, page = 0, size = 1000) => {
|
||||
const params = { page, size, moduleId };
|
||||
return apiService.get(`${API_BASE_URL}/report-builder-by-id`, params);
|
||||
},
|
||||
|
||||
create: (fbHeader, moduleId) => {
|
||||
const params = { moduleId };
|
||||
return apiService.post(`${API_BASE_URL}/report-builder`, fbHeader, { params });
|
||||
},
|
||||
|
||||
createservicereport: (fbHeader, moduleId) => {
|
||||
const params = { moduleId };
|
||||
return apiService.post(`${API_BASE_URL}/report-builder_service`, fbHeader, { params });
|
||||
},
|
||||
|
||||
createQuery: (reportId) => {
|
||||
const params = { reportId };
|
||||
return apiService.post(`${API_BASE_URL}/add-master-query`, {}, { params });
|
||||
},
|
||||
|
||||
update: (id, functionRegister) => {
|
||||
return apiService.put(`${API_BASE_URL}/add-master-query/${id}`, functionRegister);
|
||||
},
|
||||
|
||||
updateservicereport: (id, functionRegister) => {
|
||||
return apiService.put(`${API_BASE_URL}/updatereport/${id}`, functionRegister);
|
||||
},
|
||||
|
||||
getMasterQuery: (id) => apiService.get(`${API_BASE_URL}/master-query/${id}`),
|
||||
|
||||
getMasterData: (query) => {
|
||||
const params = { sql_query: query };
|
||||
return apiService.get(`${API_BASE_URL}/master-query-data`, params);
|
||||
},
|
||||
|
||||
report2: (serviceName) => apiService.post(`${API_BASE_URL}/add-report`, serviceName),
|
||||
|
||||
saveq: (data) => apiService.post('FndQuery/query', data),
|
||||
|
||||
getall: () => apiService.get('FndQuery/query'),
|
||||
|
||||
getreportdata: (apiName) => {
|
||||
const url = `${API_BASE_URL}/${apiName}`;
|
||||
return apiService.get(url);
|
||||
},
|
||||
|
||||
getDatabase: () => apiService.get('SqlworkbenchSqlcont/sql'),
|
||||
|
||||
getTableListn: (val) => apiService.get(`Table_list/${val}`), // table list
|
||||
|
||||
getcolListn: (val, val1) => apiService.get(`Table_list/${val}/${val1}`), // column list
|
||||
|
||||
getColumnList: (tableSchema, tables) => {
|
||||
const params = { str: tables.join(',') };
|
||||
return apiService.get(`${API_BASE_URL}/AllTable_list/${tableSchema}`, { params });
|
||||
},
|
||||
|
||||
getAllColumnsFromAllTables: (tableNames) => {
|
||||
// If tableNames is an array, join it with commas
|
||||
const tables = Array.isArray(tableNames) ? tableNames.join(',') : tableNames;
|
||||
return apiService.get(`Alias_Table_list/${tables}`);
|
||||
},
|
||||
|
||||
getcollist: (table) => apiService.get(`fndMenu/loadcolumn/${table}`),
|
||||
|
||||
createdb: (data) => apiService.post('SqlworkbenchSqlcont/sql', data),
|
||||
|
||||
updateSqlModel: (id, sqlModel) => apiService.put(`SqlworkbenchSqlcont/sql/${id}`, sqlModel),
|
||||
|
||||
getSqlModelById: (id) => apiService.get(`SqlworkbenchSqlcont/sql/${id}`),
|
||||
|
||||
deleteSqlModel: (id) => apiService.delete(`SqlworkbenchSqlcont/sql/${id}`),
|
||||
|
||||
getallentity: () => apiService.get(`${API_BASE_URL}/report-builder`),
|
||||
|
||||
saveData: (data) => apiService.post('Rpt_builder/Rpt_builder', data),
|
||||
|
||||
getDetails: () => apiService.get('Rpt_builder/Rpt_builder'),
|
||||
|
||||
getDetailsById: (id) => apiService.get(`Rpt_builder/Rpt_builder/${id}`),
|
||||
|
||||
deleteById: (id) => apiService.delete(`Rpt_builder/Rpt_builder/${id}`),
|
||||
|
||||
updateData: (data, id) => apiService.put(`Rpt_builder/Rpt_builder/${id}`, data),
|
||||
|
||||
saverbData: (data) => apiService.post('Rpt_builder2/Rpt_builder2', data),
|
||||
|
||||
getrbDetails: () => apiService.get('Rpt_builder2/Rpt_builder2'),
|
||||
|
||||
getrbDetailsById: (id) => apiService.get(`Rpt_builder2/Rpt_builder2/${id}`),
|
||||
|
||||
deletrbById: (id) => apiService.delete(`Rpt_builder2/Rpt_builder2/${id}`),
|
||||
|
||||
updaterbData: (data, id) => apiService.put(`Rpt_builder2/Rpt_builder2/${id}`, data),
|
||||
|
||||
updaterbLineData: (data, id) => apiService.put(`Rpt_builder2_lines/update/${id}`, data),
|
||||
|
||||
getrbLineDetailsById: (id) => apiService.get(`Rpt_builder2_lines/Rpt_builder2_lines/${id}`),
|
||||
|
||||
getStdParamById: (id) => apiService.get(`Rpt_builder2/html/build_report2/${id}`),
|
||||
|
||||
getcolumnDetailsByurl: (url) => apiService.get(`Rpt_builder2_lines/geturlkeybyurl?url=${url}`),
|
||||
|
||||
getAllDetailsByurl: (url) => apiService.get(`Rpt_builder2_lines/fetch_data_url?url=${url}`),
|
||||
|
||||
downloadFile: async (format, dataList, name) => {
|
||||
const url = `${API_BASE_URL}/rbbuilder/fileconverter/downloadFile/${format}`;
|
||||
try {
|
||||
const fileData = await apiService.post(url, dataList, { responseType: 'blob' });
|
||||
const blob = new Blob([fileData], { type: 'application/octet-stream' });
|
||||
const fileName = name ? `${name}.${format}` : `download.${format}`;
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = fileName;
|
||||
link.click();
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default ReportBuilderService;
|
||||
@@ -0,0 +1,49 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
// Function to run a specific report
|
||||
export const runReport = async (reportId) => {
|
||||
try {
|
||||
const response = await apiService.get(`Rpt_builder2/Rpt_builder2/${reportId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error running report:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to fetch all reports
|
||||
export const fetchAllReportsApi = async () => {
|
||||
try {
|
||||
const response = await apiService.get('Rpt_builder2/Rpt_builder2');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching reports:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to fetch standard parameters
|
||||
export const fetchStandardParameters = async (url) => {
|
||||
try {
|
||||
const response = await apiService.get(`Rpt_builder2_lines/fetch_data_url?url=${encodeURIComponent(url)}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching parameters:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// File download function
|
||||
export const downloadFile = async (format, dataList, name) => {
|
||||
try {
|
||||
const blob = await apiService.downloadFile(
|
||||
`rbbuilder/fileconverter/downloadFile/${format}`,
|
||||
dataList
|
||||
);
|
||||
saveAs(blob, `${name}.${format}`);
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// src/api/ReportSqlBuilderApi.js
|
||||
|
||||
import apiService from '../APIRequestService/APIServices';// Assuming apiService is in the same directory
|
||||
|
||||
|
||||
|
||||
const BASE_URL = `${process.env.REACT_APP_API_BASE_URL}/Rpt_builder2/Rpt_builder2`;
|
||||
console.log("BASE_URL:", BASE_URL); // Log the base URL for debugging
|
||||
const ReportSqlBuilderApi = {
|
||||
fetchUserDetails: async () => {
|
||||
try {
|
||||
const response = await apiService.get(BASE_URL);
|
||||
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 default ReportSqlBuilderApi;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import apiService from '../APIRequestService/APIService';
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
|
||||
|
||||
|
||||
export const UpdateReportSQLBuilder = async (id,formData) => {
|
||||
const BASE_URL = `${process.env.REACT_APP_API_BASE_URL}/Rpt_builder2/Rpt_builder2/${id}`;
|
||||
try {
|
||||
const response = await apiService.put(BASE_URL, formData);
|
||||
console.log("update response: ",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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// reportBaseUrlAddAPI.js
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
// Function to handle the Add API request
|
||||
export const addReport = async (formData) => {
|
||||
try {
|
||||
const response = await apiService.post('/Rpt_builder2/Rpt_builder2', formData);
|
||||
|
||||
console.log("add Report response: ",response)
|
||||
return response; // Return the response to handle success in the component
|
||||
} catch (error) {
|
||||
throw error; // Re-throw error to handle it in the calling component
|
||||
}
|
||||
};
|
||||
|
||||
// Function to fetch keys from URL
|
||||
export const getKeysFromUrl = async (url) => {
|
||||
try {
|
||||
const response = await apiService.get(`/api/report-builder/columns`, { url });
|
||||
return response; // Return the response for further processing
|
||||
} catch (error) {
|
||||
throw error; // Re-throw error to handle it in the calling component
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Create axios instance
|
||||
const apiService = axios.create({
|
||||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||||
});
|
||||
|
||||
// Add request interceptor to inject the token
|
||||
apiService.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Add response interceptor to handle 401 errors
|
||||
apiService.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Handle token expiration (e.g., redirect to login)
|
||||
localStorage.removeItem('authToken');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default apiService;
|
||||
@@ -0,0 +1,12 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
|
||||
export const deleteReport = async (reportId) => {
|
||||
try {
|
||||
const response = await apiService.delete(`/Rpt_builder2/Rpt_builder2/${reportId}`);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import apiService from '../APIRequestService/APIServices';
|
||||
|
||||
const editReport = async (id, payload) => {
|
||||
try {
|
||||
const response = await apiService.put(`Rpt_builder2_lines/update/${id}`, payload);
|
||||
console.log("Edit api report base url ", response.data)
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error in editReport:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
editReport,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/* src/components/Login.css */
|
||||
/* .login-container {
|
||||
width: 300px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border: 2px solid #3498db; /* Blue border */
|
||||
/* border-radius: 10px;
|
||||
background: linear-gradient(to right, #3498db, #2980b9); /* Gradient background */
|
||||
/* color: white;
|
||||
text-align: center;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); /* Box shadow for depth */
|
||||
/* } */
|
||||
|
||||
/* .login-container label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
} */
|
||||
|
||||
/* .login-container input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.login-container button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
} */
|
||||
|
||||
/* .login-container a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login-container a:hover {
|
||||
text-decoration: underline;
|
||||
} */
|
||||
|
||||
|
||||
/* .login-container input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
} */
|
||||
122
testreactdatatype-demot1-f/authsec_react_materail_ui/src/App.js
Normal file
122
testreactdatatype-demot1-f/authsec_react_materail_ui/src/App.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
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 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 DynamicTable from "./components/Dashboard/Dynamictable";
|
||||
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";
|
||||
import ReportRunnerAll from "./components/Dashboard/reports/reportrunnerall";
|
||||
import ReportBuilderSQL from "./components/Dashboard/reports/reportbuildersql";
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#1976d2',
|
||||
},
|
||||
secondary: {
|
||||
main: '#dc004e',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SystemParameterProvider>
|
||||
<BrowserRouter>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={1500}
|
||||
hideProgressBar={false}
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
|
||||
<Routes>
|
||||
<Route path="/login" 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="user-report" element={<ReportBuilderSQL />} />
|
||||
{/* <Route path="reports" element={<ReportRunnerAll />} /> */}
|
||||
<Route path="about" element={<About />} />
|
||||
<Route path="profile" element={<Profile />} />
|
||||
</Route>
|
||||
{/* buildercomponents */}
|
||||
|
||||
|
||||
|
||||
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</SystemParameterProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,468 @@
|
||||
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;
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
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;
|
||||
@@ -0,0 +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("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;
|
||||
@@ -0,0 +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;
|
||||
@@ -0,0 +1,217 @@
|
||||
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;
|
||||
@@ -0,0 +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 */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +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;
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
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;
|
||||
@@ -0,0 +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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
@@ -0,0 +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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
@@ -0,0 +1,219 @@
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,746 @@
|
||||
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;
|
||||
@@ -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 ,
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
@@ -0,0 +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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
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";
|
||||
import ReportRunnerAll from "./reports/reportrunnerall";
|
||||
import Report from "./reports/Report";
|
||||
import ReportBuilderSQL from "./reports/reportbuildersql";
|
||||
|
||||
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="user-report" element={<ReportBuilderSQL/>} />
|
||||
<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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +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;
|
||||
@@ -0,0 +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;
|
||||
@@ -0,0 +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 */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import {
|
||||
Container,
|
||||
Grid,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
Typography,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
Paper,
|
||||
Button,
|
||||
Avatar,
|
||||
Chip,
|
||||
Box,
|
||||
IconButton,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AddCircleOutline,
|
||||
Storage as Database,
|
||||
Link as LinkIcon,
|
||||
CheckCircle,
|
||||
Cancel,
|
||||
MoreVert,
|
||||
AccessTime
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const Report = () => {
|
||||
const [reports, setReports] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
console.log("API Base URL:", api);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
|
||||
if (!token) {
|
||||
setError("No auth token found. Please login.");
|
||||
toast.error("Authentication required");
|
||||
navigate("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${api}/Rpt_builder2/Rpt_builder2`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status === 401 ? "Session expired" : "Failed to fetch data");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setReports(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError(error.message);
|
||||
if (error.message === "Session expired") {
|
||||
toast.error("Session expired. Please login again.");
|
||||
navigate("/login");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [api, navigate]);
|
||||
|
||||
const goToAddSQLReport = () => {
|
||||
navigate("/dashboard/user-report");
|
||||
};
|
||||
|
||||
const goToAddURLReport = () => {
|
||||
navigate("/admin/reportbuild2all");
|
||||
};
|
||||
|
||||
const handleCardClick = (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 }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
{/* Header Section */}
|
||||
<Paper elevation={2} sx={{
|
||||
p: 3,
|
||||
mb: 4,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderRadius: '12px'
|
||||
}}>
|
||||
<Typography variant="h4" color="primary" sx={{ fontWeight: 'bold' }}>
|
||||
All Reports
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddCircleOutline />}
|
||||
onClick={goToAddSQLReport}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Report Builder SQL
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddCircleOutline />}
|
||||
onClick={goToAddURLReport}
|
||||
>
|
||||
Report Builder URL
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<Grid container justifyContent="center" sx={{ py: 8 }}>
|
||||
<CircularProgress size={60} />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Error State */}
|
||||
{error && !loading && (
|
||||
<Alert severity="error" sx={{ mb: 4 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Reports Grid */}
|
||||
{!loading && reports.length > 0 && (
|
||||
<Grid container spacing={3}>
|
||||
{reports.map((report) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={report.id}>
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.3s, box-shadow 0.3s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-8px)',
|
||||
boxShadow: 6
|
||||
},
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
onClick={() => handleCardClick(report)}
|
||||
>
|
||||
{/* Card Header with Gradient */}
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
background: report.isSql
|
||||
? 'linear-gradient(135deg, #f5f7fa 0%,rgb(198, 218, 255) 100%)'
|
||||
: 'linear-gradient(135deg, #fff9f0 0%,rgb(217, 204, 255) 100%)',
|
||||
height: '60px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
mr: 2,
|
||||
borderRadius: '10px',
|
||||
bgcolor: report.isSql ? 'rgba(65, 105, 225, 0.15)' : 'rgba(255, 165, 0, 0.15)',
|
||||
color: report.isSql ? '#4169E1' : '#FF8C00'
|
||||
}}
|
||||
>
|
||||
{report.isSql ? <Database /> : <LinkIcon />}
|
||||
</Avatar>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
color: report.isSql ? '#4169E1' : '#FF8C00',
|
||||
}}
|
||||
>
|
||||
{report.isSql ? "SQL Report" : "URL Report"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Status Chip */}
|
||||
<Chip
|
||||
label={report.active ? "Active" : "Inactive"}
|
||||
size="small"
|
||||
color={report.active ? "success" : "error"}
|
||||
icon={report.active ? <CheckCircle fontSize="small" /> : <Cancel fontSize="small" />}
|
||||
sx={{
|
||||
backgroundColor: report.active ? 'rgba(40, 167, 69, 0.1)' : 'rgba(220, 53, 69, 0.1)',
|
||||
color: report.active ? '#28a745' : '#dc3545',
|
||||
px: 1,
|
||||
py: 0.5
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Card Content */}
|
||||
<CardContent sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="h6"
|
||||
component="div"
|
||||
noWrap
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
color: 'text.primary',
|
||||
mb: 2
|
||||
}}
|
||||
>
|
||||
{report.reportName}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
minHeight: '40px',
|
||||
lineHeight: 1.5,
|
||||
fontSize: '0.9rem'
|
||||
}}
|
||||
>
|
||||
{report.description || "No description available"}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
||||
{/* Card Footer */}
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
backgroundColor: 'background.default'
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<AccessTime fontSize="small" sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Updated: {new Date(report.updatedAt).toLocaleDateString()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: 'action.hover',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.selected'
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Add menu functionality here
|
||||
}}
|
||||
>
|
||||
<MoreVert fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{!loading && reports.length === 0 && !error && (
|
||||
<Paper sx={{
|
||||
p: 4,
|
||||
textAlign: 'center',
|
||||
borderRadius: '12px'
|
||||
}}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
No reports available
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Create your first report by clicking one of the "Add Report" buttons
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddCircleOutline />}
|
||||
onClick={goToAddSQLReport}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Add SQL Report
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddCircleOutline />}
|
||||
onClick={goToAddURLReport}
|
||||
>
|
||||
Add URL Report
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Report;
|
||||
@@ -0,0 +1,773 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Pagination,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Add,
|
||||
ArrowBack,
|
||||
CheckCircle,
|
||||
Close,
|
||||
Delete,
|
||||
Edit,
|
||||
MoreVert,
|
||||
PlayArrow,
|
||||
Search,
|
||||
Settings,
|
||||
ViewList,
|
||||
} from "@mui/icons-material";
|
||||
import ReportSqlBuilderApi from "./../../../ApiServices/ReportSQLBuilderAPI";
|
||||
import { addSQLReport } from "./../../../ApiServices/AddReportSQLBuilderAPI";
|
||||
import { UpdateReportSQLBuilder } from "./../../../ApiServices/UpdateReportSQLBuilderAPI";
|
||||
import { deleteReportSQL } from "./../../../ApiServices/DeleteReportSQLAPI";
|
||||
import ReportBuilderService from "../../../ApiServices/ReportBuilderService";
|
||||
|
||||
const ReportBuilderSQL = () => {
|
||||
const [userDetails, setUserDetails] = useState([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const navigate = useNavigate();
|
||||
const [visibleColumns, setVisibleColumns] = useState({
|
||||
reportName: true,
|
||||
description: true,
|
||||
action: true,
|
||||
status: true,
|
||||
});
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [addDialogOpen, setAddDialogOpen] = 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 [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [itemToDelete, setItemToDelete] = useState({ id: null, name: "" });
|
||||
const [reportConfigOpen, setReportConfigOpen] = 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 [columnsMenuAnchor, setColumnsMenuAnchor] = useState(null);
|
||||
const [rowsMenuAnchor, setRowsMenuAnchor] = useState(null);
|
||||
|
||||
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 handleSearch = (e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const filteredData = userDetails.filter((item) =>
|
||||
item.reportName?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(filteredData.length / rowsPerPage);
|
||||
const paginatedData = filteredData.slice(
|
||||
(currentPage - 1) * rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
);
|
||||
|
||||
const handleEdit = (report) => {
|
||||
setEditData(report);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditSubmit = async () => {
|
||||
try {
|
||||
const updatedReport = await UpdateReportSQLBuilder(editData.id, editData);
|
||||
setUserDetails(
|
||||
userDetails.map((detail) =>
|
||||
detail.id === editData.id ? { ...detail, ...editData } : detail
|
||||
)
|
||||
);
|
||||
setEditDialogOpen(false);
|
||||
toast.success("Report updated successfully");
|
||||
} catch (error) {
|
||||
console.error("Error updating report:", error);
|
||||
toast.error("Failed to update report");
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddSubmit = async () => {
|
||||
try {
|
||||
const addedReport = await addSQLReport(newData);
|
||||
setUserDetails([...userDetails, addedReport]);
|
||||
setAddDialogOpen(false);
|
||||
setNewData({
|
||||
reportName: "",
|
||||
description: "",
|
||||
active: false,
|
||||
isSql: true,
|
||||
Rpt_builder2_lines: [{ model: "" }],
|
||||
});
|
||||
toast.success("Report added successfully");
|
||||
} catch (error) {
|
||||
console.error("Error adding report:", error);
|
||||
toast.error("Failed to add report");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteReportSQL(itemToDelete.id);
|
||||
setUserDetails(userDetails.filter((item) => item.id !== itemToDelete.id));
|
||||
setDeleteConfirmOpen(false);
|
||||
toast.success("Report deleted successfully");
|
||||
} catch (error) {
|
||||
console.error("Error deleting report:", error);
|
||||
toast.error("Failed to delete report");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDatabaseConnections = async () => {
|
||||
try {
|
||||
const response = await ReportBuilderService.getDatabase();
|
||||
setDatabaseConnections(response?.data || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching database connections:", error);
|
||||
toast.error("Failed to load database connections");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowClick = async (report) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await fetchDatabaseConnections();
|
||||
|
||||
const response = await ReportBuilderService.getrbLineDetailsById(
|
||||
report.rpt_builder2_lines[0].id
|
||||
);
|
||||
|
||||
let configData = {};
|
||||
if (report.Rpt_builder2_lines?.length > 0) {
|
||||
try {
|
||||
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,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error parsing report config:", error);
|
||||
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);
|
||||
setReportConfigOpen(true);
|
||||
} catch (error) {
|
||||
console.error("Error fetching report config:", error);
|
||||
toast.error("Failed to load report configuration");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
await ReportBuilderService.updaterbLineData(
|
||||
saveReportData,
|
||||
reportConfigData.id
|
||||
);
|
||||
setReportConfigOpen(false);
|
||||
toast.success("Report configuration updated");
|
||||
fetchUserDetails();
|
||||
} catch (error) {
|
||||
console.error("Error updating report config:", error);
|
||||
toast.error("Failed to update report configuration");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const goToRunner = (report) => {
|
||||
if (!report || typeof report.isSql === "undefined") return;
|
||||
navigate(
|
||||
report.isSql
|
||||
? `/admin/report-runner/${report.id}`
|
||||
: `/admin/report-runner2/${report.id}`
|
||||
);
|
||||
};
|
||||
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns((prev) => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<CardContent>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h5" component="h1">
|
||||
Report Builder (SQL)
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<PlayArrow />}
|
||||
onClick={() => navigate("/dashboard/reports")}
|
||||
>
|
||||
Report Runner
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<Add />}
|
||||
onClick={() => setAddDialogOpen(true)}
|
||||
>
|
||||
Add Report
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Grid container spacing={2} alignItems="center" sx={{ mb: 2 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={handleSearch}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Search sx={{ color: "action.active", mr: 1 }} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={(e) => setColumnsMenuAnchor(e.currentTarget)}
|
||||
startIcon={<ViewList />}
|
||||
>
|
||||
Manage Columns
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={columnsMenuAnchor}
|
||||
open={Boolean(columnsMenuAnchor)}
|
||||
onClose={() => setColumnsMenuAnchor(null)}
|
||||
>
|
||||
{Object.keys(visibleColumns).map((column) => (
|
||||
<MenuItem key={column} onClick={() => toggleColumn(column)}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox checked={visibleColumns[column]} readOnly />
|
||||
}
|
||||
label={column.charAt(0).toUpperCase() + column.slice(1)}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={(e) => setRowsMenuAnchor(e.currentTarget)}
|
||||
endIcon={<MoreVert />}
|
||||
>
|
||||
{rowsPerPage} rows
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={rowsMenuAnchor}
|
||||
open={Boolean(rowsMenuAnchor)}
|
||||
onClose={() => setRowsMenuAnchor(null)}
|
||||
>
|
||||
{[5, 10, 20, 50].map((num) => (
|
||||
<MenuItem
|
||||
key={num}
|
||||
onClick={() => {
|
||||
setRowsPerPage(num);
|
||||
setRowsMenuAnchor(null);
|
||||
}}
|
||||
>
|
||||
{num}
|
||||
{rowsPerPage === num && (
|
||||
<CheckCircle sx={{ ml: 1, color: "primary.main" }} />
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Go To</TableCell>
|
||||
{visibleColumns.reportName && <TableCell>Report Name</TableCell>}
|
||||
{visibleColumns.description && <TableCell>Description</TableCell>}
|
||||
{visibleColumns.status && <TableCell>Status</TableCell>}
|
||||
{visibleColumns.action && <TableCell>Actions</TableCell>}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{paginatedData.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} align="center">
|
||||
No records found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedData
|
||||
.filter((detail) => detail.isSql)
|
||||
.map((detail) => (
|
||||
<TableRow
|
||||
key={detail.id}
|
||||
hover
|
||||
onClick={() => handleRowClick(detail)}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRowClick(detail);
|
||||
}}
|
||||
>
|
||||
<Settings color="primary" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{visibleColumns.reportName && (
|
||||
<TableCell>{detail.reportName}</TableCell>
|
||||
)}
|
||||
{visibleColumns.description && (
|
||||
<TableCell>
|
||||
{detail.description || "No description available"}
|
||||
</TableCell>
|
||||
)}
|
||||
{visibleColumns.status && (
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={detail.active ? "Active" : "Inactive"}
|
||||
color={detail.active ? "success" : "error"}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{visibleColumns.action && (
|
||||
<TableCell>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEdit(detail);
|
||||
}}
|
||||
>
|
||||
<Edit color="primary" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setItemToDelete({
|
||||
id: detail.id,
|
||||
name: detail.reportName,
|
||||
});
|
||||
setDeleteConfirmOpen(true);
|
||||
}}
|
||||
>
|
||||
<Delete color="error" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing {(currentPage - 1) * rowsPerPage + 1} to{" "}
|
||||
{Math.min(currentPage * rowsPerPage, filteredData.length)} of{" "}
|
||||
{filteredData.length} entries
|
||||
</Typography>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={currentPage}
|
||||
onChange={(e, page) => setCurrentPage(page)}
|
||||
color="primary"
|
||||
/>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Edit Dialog */}
|
||||
<Dialog open={editDialogOpen} onClose={() => setEditDialogOpen(false)}>
|
||||
<DialogTitle>
|
||||
Edit Report
|
||||
<IconButton
|
||||
sx={{ position: "absolute", right: 8, top: 8 }}
|
||||
onClick={() => setEditDialogOpen(false)}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Report Name"
|
||||
value={editData.reportName || ""}
|
||||
onChange={(e) =>
|
||||
setEditData({ ...editData, reportName: e.target.value })
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Description"
|
||||
value={editData.description || ""}
|
||||
onChange={(e) =>
|
||||
setEditData({ ...editData, description: e.target.value })
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={editData.active || false}
|
||||
onChange={(e) =>
|
||||
setEditData({ ...editData, active: e.target.checked })
|
||||
}
|
||||
/>
|
||||
}
|
||||
label="Active"
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setEditDialogOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleEditSubmit}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Add Dialog */}
|
||||
<Dialog open={addDialogOpen} onClose={() => setAddDialogOpen(false)}>
|
||||
<DialogTitle>
|
||||
Add New Report
|
||||
<IconButton
|
||||
sx={{ position: "absolute", right: 8, top: 8 }}
|
||||
onClick={() => setAddDialogOpen(false)}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Report Name"
|
||||
value={newData.reportName}
|
||||
onChange={(e) =>
|
||||
setNewData({ ...newData, reportName: e.target.value })
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Description"
|
||||
value={newData.description}
|
||||
onChange={(e) =>
|
||||
setNewData({ ...newData, description: e.target.value })
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={newData.active}
|
||||
onChange={(e) =>
|
||||
setNewData({ ...newData, active: e.target.checked })
|
||||
}
|
||||
/>
|
||||
}
|
||||
label="Active"
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setAddDialogOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleAddSubmit}>
|
||||
Add Report
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={deleteConfirmOpen}
|
||||
onClose={() => setDeleteConfirmOpen(false)}
|
||||
>
|
||||
<DialogTitle>Confirm Deletion</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete "{itemToDelete.name}"? This action
|
||||
cannot be undone.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteConfirmOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" color="error" onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Report Configuration Dialog */}
|
||||
<Dialog
|
||||
open={reportConfigOpen}
|
||||
onClose={() => setReportConfigOpen(false)}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
Report Configuration
|
||||
<IconButton
|
||||
sx={{ position: "absolute", right: 8, top: 8 }}
|
||||
onClick={() => setReportConfigOpen(false)}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Connection Name"
|
||||
name="conn_name"
|
||||
value={reportConfigData.conn_name}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
conn_name: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<MenuItem value="">Select Database Connection</MenuItem>
|
||||
{databaseConnections.map((db) => (
|
||||
<MenuItem key={db.name} value={db.name}>
|
||||
{db.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Date Parameter Required"
|
||||
name="date_param_req"
|
||||
value={reportConfigData.date_param_req}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
date_param_req: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<MenuItem value="">Select Option</MenuItem>
|
||||
<MenuItem value="Yes">Yes</MenuItem>
|
||||
<MenuItem value="No">No</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={5}
|
||||
label="SQL String"
|
||||
name="sql_str"
|
||||
value={reportConfigData.sql_str}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
sql_str: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
InputProps={{
|
||||
style: { fontFamily: "monospace" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
label="Column String (html)"
|
||||
name="column_str"
|
||||
value={reportConfigData.column_str}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
column_str: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
label="Standard Parameter String (html)"
|
||||
name="std_param_html"
|
||||
value={reportConfigData.std_param_html}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
std_param_html: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
label="Adhoc Parameter String (html)"
|
||||
name="adhoc_param_html"
|
||||
value={reportConfigData.adhoc_param_html}
|
||||
onChange={(e) =>
|
||||
setReportConfigData({
|
||||
...reportConfigData,
|
||||
adhoc_param_html: e.target.value,
|
||||
})
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setReportConfigOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleReportConfigUpdate}>
|
||||
Update Configuration
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportBuilderSQL;
|
||||
@@ -0,0 +1,264 @@
|
||||
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';
|
||||
import {
|
||||
Container,
|
||||
Grid,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
CardActions,
|
||||
Typography,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Avatar,
|
||||
IconButton,
|
||||
Chip,
|
||||
Divider,
|
||||
Paper
|
||||
} from '@mui/material';
|
||||
import {
|
||||
AddCircleOutline,
|
||||
Link,
|
||||
Storage as Database,
|
||||
MoreVert,
|
||||
AccessTime,
|
||||
Delete,
|
||||
CheckCircle,
|
||||
Cancel
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const ReportRunnerAll = () => {
|
||||
const [gridData, setGridData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [rowSelected, setRowSelected] = useState(null);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllReports();
|
||||
}, []);
|
||||
|
||||
const fetchAllReports = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await fetchAllReportsApi();
|
||||
console.log("Fetched all reports:", data);
|
||||
setGridData(data);
|
||||
if (data.length === 0) setError("No data available");
|
||||
} catch (error) {
|
||||
console.error("Error while fetching reports:", error);
|
||||
setError("Error fetching data.");
|
||||
toast.error("Failed to load reports");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const goToAdd = () => navigate("/admin/user-report");
|
||||
const goToAdd2 = () => navigate("/admin/reportbuild2all");
|
||||
|
||||
const goToRunner = (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) => {
|
||||
setOpenDeleteDialog(false);
|
||||
try {
|
||||
const response = await fetch(`/api/report-builder/${id}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
if (response.ok) {
|
||||
toast.success("Report deleted successfully");
|
||||
fetchAllReports();
|
||||
} else {
|
||||
throw new Error("Failed to delete");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Error deleting report");
|
||||
}
|
||||
};
|
||||
|
||||
const openDeleteModal = (row) => {
|
||||
setRowSelected(row);
|
||||
setOpenDeleteDialog(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
{/* Header */}
|
||||
<Paper elevation={2} sx={{ p: 3, mb: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="h4" color="primary">
|
||||
<Database sx={{ verticalAlign: 'middle', mr: 1 }} />
|
||||
All Reports
|
||||
</Typography>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddCircleOutline />}
|
||||
onClick={goToAdd}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
SQL Report
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<Link />}
|
||||
onClick={goToAdd2}
|
||||
>
|
||||
URL Report
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
{/* Loading state */}
|
||||
{isLoading && (
|
||||
<Grid container justifyContent="center" sx={{ py: 8 }}>
|
||||
<CircularProgress size={60} />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Error state */}
|
||||
{error && !isLoading && (
|
||||
<Alert severity="error" sx={{ mb: 4 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Report Cards */}
|
||||
{!isLoading && gridData.length > 0 && (
|
||||
<Grid container spacing={3}>
|
||||
{gridData.map((report, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.3s, box-shadow 0.3s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-8px)',
|
||||
boxShadow: 6
|
||||
}
|
||||
}}
|
||||
onClick={() => goToRunner(report)}
|
||||
>
|
||||
{/* Card Header */}
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar sx={{
|
||||
bgcolor: report.isSql ? 'primary.light' : 'secondary.light',
|
||||
color: report.isSql ? 'primary.main' : 'secondary.main'
|
||||
}}>
|
||||
{report.isSql ? <Database /> : <Link />}
|
||||
</Avatar>
|
||||
}
|
||||
action={
|
||||
<Chip
|
||||
label={report.active ? "Active" : "Inactive"}
|
||||
size="small"
|
||||
color={report.active ? "success" : "error"}
|
||||
icon={report.active ? <CheckCircle fontSize="small" /> : <Cancel fontSize="small" />}
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
{report.isSql ? "SQL Report" : "URL Report"}
|
||||
</Typography>
|
||||
}
|
||||
sx={{
|
||||
bgcolor: report.isSql ? 'primary.50' : 'secondary.50',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Card Content */}
|
||||
<CardContent sx={{ flexGrow: 1 }}>
|
||||
<Typography gutterBottom variant="h6" component="div" noWrap>
|
||||
{report.reportName}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
minHeight: '40px'
|
||||
}}>
|
||||
{report.description || "No description available"}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
||||
{/* Card Footer */}
|
||||
<Divider />
|
||||
<CardActions sx={{ justifyContent: 'space-between', p: 2 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<AccessTime fontSize="small" sx={{ mr: 0.5 }} />
|
||||
Updated: {new Date(report.updatedAt).toLocaleDateString()}
|
||||
</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openDeleteModal(report);
|
||||
}}
|
||||
>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Delete Dialog */}
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
>
|
||||
<DialogTitle>Delete Confirmation</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Are you sure you want to delete the report?
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" sx={{ mt: 1 }}>
|
||||
{rowSelected?.reportName}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={() => handleDelete(rowSelected?.id)}
|
||||
color="error"
|
||||
startIcon={<Delete />}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportRunnerAll;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
/* 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 */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,111 @@
|
||||
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;
|
||||
@@ -0,0 +1,99 @@
|
||||
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;
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user