material
This commit is contained in:
9374
package-lock.json
generated
9374
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -2,15 +2,12 @@
|
|||||||
"name": "log",
|
"name": "log",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@mui/styles": "^5.16.4",
|
"@mui/styles": "^5.16.4",
|
||||||
@@ -20,19 +17,36 @@
|
|||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"ajv": "^6.12.6",
|
||||||
|
"ajv-keywords": "^3.5.2",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"lucide-react": "^0.511.0",
|
"chart.js": "^4.4.9",
|
||||||
"mdb-react-ui-kit": "^7.1.0",
|
"mdb-react-ui-kit": "^7.1.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-barcode": "^1.5.3",
|
"react-barcode": "^1.5.3",
|
||||||
"react-data-grid": "^7.0.0-beta.44",
|
"react-chartjs-2": "^5.3.0",
|
||||||
|
"react-data-grid": "^6.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.2.1",
|
"react-grid-layout": "^1.5.1",
|
||||||
|
"react-hook-form": "^7.57.0",
|
||||||
|
"react-hot-toast": "^2.5.2",
|
||||||
|
"react-i18next": "^15.5.2",
|
||||||
"react-qr-code": "^2.0.14",
|
"react-qr-code": "^2.0.14",
|
||||||
"react-router-dom": "^6.21.3",
|
"react-router-dom": "^6.21.3",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"react-toastify": "^11.0.5",
|
||||||
|
"schema-utils": "^3.3.0",
|
||||||
|
"web-vitals": "^2.1.4",
|
||||||
|
"webpack-dev-server": "^4.15.1",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
content="Web site created using create-react-app"
|
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 href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
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/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>CloudnSure</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
117
src/APIRequestService/APIServices.js
Normal file
117
src/APIRequestService/APIServices.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { getToken } from '../../src/utils/tokenService';
|
||||||
|
|
||||||
|
const BASE_URL = process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: BASE_URL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Add a request interceptor to include Authorization header
|
||||||
|
apiClient.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
console.log("token: ",token);
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generic error handler function
|
||||||
|
const handleError = (error) => {
|
||||||
|
let errorMessage = 'An unknown error occurred';
|
||||||
|
console.error('Error Details:', error);
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
// HTTP errors
|
||||||
|
switch (error.response.status) {
|
||||||
|
case 401:
|
||||||
|
errorMessage = 'Unauthorized - Please login again.';
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
errorMessage = 'Forbidden - Access denied.';
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
errorMessage = 'Service not found.';
|
||||||
|
break;
|
||||||
|
case 408:
|
||||||
|
errorMessage = 'Request timed out.';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
errorMessage = 'Internal server error.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = `Unexpected error: ${error.response.statusText || 'Server Error'}`;
|
||||||
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
// No response received
|
||||||
|
errorMessage = 'No response from server. Please check your connection.';
|
||||||
|
} else {
|
||||||
|
// Other errors
|
||||||
|
errorMessage = error.message || 'An unexpected error occurred.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(errorMessage); // Return error message as rejected promise
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the reusable methods
|
||||||
|
// const apiService = {
|
||||||
|
// get: (url, params) =>
|
||||||
|
// apiClient
|
||||||
|
// .get(url, { params: params || {} })
|
||||||
|
// .catch(handleError), // Attach error handler
|
||||||
|
// post: (url, body = {}) =>
|
||||||
|
// apiClient
|
||||||
|
// .post(url, body)
|
||||||
|
// .catch(handleError), // Attach error handler
|
||||||
|
// put: (url, body = {}) =>
|
||||||
|
// apiClient
|
||||||
|
// .put(url, body)
|
||||||
|
// .catch(handleError), // Attach error handler
|
||||||
|
// delete: (url) =>
|
||||||
|
// apiClient
|
||||||
|
// .delete(url)
|
||||||
|
// .catch(handleError), // Attach error handler
|
||||||
|
// };
|
||||||
|
|
||||||
|
const apiService = {
|
||||||
|
get: (url, params) =>
|
||||||
|
apiClient
|
||||||
|
.get(url, { params: params || {} })
|
||||||
|
.catch(handleError), // Attach error handler
|
||||||
|
|
||||||
|
post: (url, body = {}, options = {}) =>
|
||||||
|
apiClient
|
||||||
|
.post(url, body, options) // Pass options such as headers
|
||||||
|
.catch(handleError), // Attach error handler
|
||||||
|
|
||||||
|
put: (url, body = {}, options = {}) =>
|
||||||
|
apiClient
|
||||||
|
.put(url, body, options) // Pass options such as headers
|
||||||
|
.catch(handleError), // Attach error handler
|
||||||
|
|
||||||
|
delete: (url, options = {}) =>
|
||||||
|
apiClient
|
||||||
|
.delete(url, options) // Pass options such as headers
|
||||||
|
.catch(handleError), // Attach error handler
|
||||||
|
};
|
||||||
|
// Add at the bottom of APIService.js
|
||||||
|
|
||||||
|
export const getSubmenuItems = async (id) => {
|
||||||
|
const response = await apiService.get(`/api1/submenu1/${id}`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
export const addSubmenuItem = async (menuId, submenuData) => {
|
||||||
|
const response = await apiService.post(`/api1/menu/${menuId}/submenu`, submenuData);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
export const updateMenuItem = (id, formData) => apiService.put(`/api1/submenu1/${id}`, formData);
|
||||||
|
export const deleteMenuItem = (id) => apiService.delete(`/api1/menu/${id}`);
|
||||||
|
|
||||||
|
|
||||||
|
export default apiService;
|
||||||
340
src/ApiServices/DashboardBuilderServices.js
Normal file
340
src/ApiServices/DashboardBuilderServices.js
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
// import axios from 'axios';
|
||||||
|
|
||||||
|
// class DashboardService {
|
||||||
|
|
||||||
|
// constructor() {
|
||||||
|
// this.baseURL = 'https://your-api-url'; // Set your base URL for API
|
||||||
|
// this.getAllURL = '/get_module_id';
|
||||||
|
// this.addDataURl = '/Savedata';
|
||||||
|
// this.deleteFieldURL = '/delete_by_header_id';
|
||||||
|
// this.getbyidURL = '/get_dashboard_headerbyid';
|
||||||
|
// this.editURL = '/update_Dashbord1_Line';
|
||||||
|
// this.updateURL = '/update_Dashbord1_Lineby_id';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get all dashboards
|
||||||
|
// getAllDash() {
|
||||||
|
// return axios.get(`${this.baseURL}/get_Dashboard_header`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching all dashboards:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get dashboards by module ID
|
||||||
|
// getAllByModuleId(module_id, page = 0, size = 1000) {
|
||||||
|
// return axios.get(`${this.baseURL}${this.getAllURL}`, {
|
||||||
|
// params: {
|
||||||
|
// page,
|
||||||
|
// size,
|
||||||
|
// module_id
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching dashboards by module ID:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Create new dashboard
|
||||||
|
// create(data) {
|
||||||
|
// return axios.post(`${this.baseURL}${this.addDataURl}`, data)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error creating dashboard:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Delete dashboard field
|
||||||
|
// deleteField(id) {
|
||||||
|
// return axios.delete(`${this.baseURL}${this.deleteFieldURL}/${id}`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error deleting dashboard field:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get dashboard by ID
|
||||||
|
// getById(id) {
|
||||||
|
// return axios.get(`${this.baseURL}${this.getbyidURL}/${id}`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching dashboard by ID:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Add or Edit dashboard
|
||||||
|
// addToDB(line) {
|
||||||
|
// return axios.put(`${this.baseURL}${this.editURL}`, line)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error updating dashboard line:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Update dashboard line data by ID
|
||||||
|
// updateLineData(id, line) {
|
||||||
|
// return axios.put(`${this.baseURL}${this.updateURL}/${id}`, line)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error updating dashboard line data:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Get dashboard count by module ID
|
||||||
|
// getCount(moduleId) {
|
||||||
|
// return axios.get(`${this.baseURL}/get_dashboard/${moduleId}`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching dashboard count:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Update dashboard header
|
||||||
|
// updateDash(dashboardHeader) {
|
||||||
|
// return axios.put(`${this.baseURL}/update_dashboard_header`, dashboardHeader)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error updating dashboard header:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Schedule methods
|
||||||
|
// saveData(data) {
|
||||||
|
// return axios.post(`${this.baseURL}/DashboardSchedule/DashboardSchedule`, data)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error saving schedule data:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getDetails() {
|
||||||
|
// return axios.get(`${this.baseURL}/DashboardSchedule/DashboardSchedule`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching schedule details:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getDetailsById(id) {
|
||||||
|
// return axios.get(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching schedule details by ID:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// deleteById(id) {
|
||||||
|
// return axios.delete(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error deleting schedule by ID:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// updateData(data, id) {
|
||||||
|
// return axios.put(`${this.baseURL}/DashboardSchedule/DashboardSchedule/${id}`, data)
|
||||||
|
// .then(response => response.data)
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error updating schedule data:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Toggle functionality
|
||||||
|
// updateToggle(value) {
|
||||||
|
// // Logic for toggle update (e.g. update a state, or some other action)
|
||||||
|
// console.log('Toggle updated to:', value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default new DashboardService();
|
||||||
|
|
||||||
|
|
||||||
|
import apiService from '../APIRequestService/APIServices';
|
||||||
|
|
||||||
|
class DashboardBuilderService {
|
||||||
|
constructor() {
|
||||||
|
this.getAllURL = '/get_module_id';
|
||||||
|
this.addDataURL = '/Savedata';
|
||||||
|
this.deleteFieldURL = '/delete_by_header_id';
|
||||||
|
this.getByIdURL = '/get_dashboard_headerbyid';
|
||||||
|
this.editURL = '/update_Dashbord1_Line';
|
||||||
|
this.updateURL = '/update_Dashbord1_Lineby_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all dashboards
|
||||||
|
// getAllDash() {
|
||||||
|
// return apiService.get('/get_Dashboard_header')
|
||||||
|
// .then(response => response.data)
|
||||||
|
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error('Error fetching all dashboards:', error);
|
||||||
|
// throw error;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
async getAllDash() {
|
||||||
|
console.log('Entering getAllDash');
|
||||||
|
try {
|
||||||
|
const response = await apiService.get('/get_Dashboard_header');
|
||||||
|
console.log('Service Response:', response);
|
||||||
|
if (!response || !response.data) {
|
||||||
|
throw new Error('Unexpected API response format.');
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching dashboards:', error);
|
||||||
|
throw new Error('Failed to fetch dashboards. Please try again later.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get dashboards by module ID
|
||||||
|
getAllByModuleId(module_id, page = 0, size = 1000) {
|
||||||
|
return apiService.get(this.getAllURL, {
|
||||||
|
params: { page, size, module_id }
|
||||||
|
})
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching dashboards by module ID:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new dashboard
|
||||||
|
create(data) {
|
||||||
|
return apiService.post(this.addDataURL, data)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error creating dashboard:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete dashboard field
|
||||||
|
deleteField(id) {
|
||||||
|
return apiService.delete(`${this.deleteFieldURL}/${id}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error deleting dashboard field:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dashboard by ID
|
||||||
|
getById(id) {
|
||||||
|
return apiService.get(`${this.getByIdURL}/${id}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching dashboard by ID:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add or Edit dashboard
|
||||||
|
addToDB(line) {
|
||||||
|
return apiService.put(this.editURL, line)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating dashboard line:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dashboard line data by ID
|
||||||
|
updateLineData(id, line) {
|
||||||
|
return apiService.put(`${this.updateURL}/${id}`, line)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating dashboard line data:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dashboard count by module ID
|
||||||
|
getCount(moduleId) {
|
||||||
|
return apiService.get(`/get_dashboard/${moduleId}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching dashboard count:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dashboard header
|
||||||
|
updateDash(dashboardHeader) {
|
||||||
|
return apiService.put('/update_dashboard_header', dashboardHeader)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating dashboard header:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule methods
|
||||||
|
saveData(data) {
|
||||||
|
return apiService.post('/DashboardSchedule/DashboardSchedule', data)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error saving schedule data:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDetails() {
|
||||||
|
return apiService.get('/DashboardSchedule/DashboardSchedule')
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching schedule details:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDetailsById(id) {
|
||||||
|
return apiService.get(`/DashboardSchedule/DashboardSchedule/${id}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching schedule details by ID:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteById(id) {
|
||||||
|
return apiService.delete(`/DashboardSchedule/DashboardSchedule/${id}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error deleting schedule by ID:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData(data, id) {
|
||||||
|
return apiService.put(`/DashboardSchedule/DashboardSchedule/${id}`, data)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating schedule data:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle functionality
|
||||||
|
updateToggle(value) {
|
||||||
|
console.log('Toggle updated to:', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DashboardBuilderService();
|
||||||
34
src/ApiServices/MenuAccessControlAPI.js
Normal file
34
src/ApiServices/MenuAccessControlAPI.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import apiService from '../APIRequestService/APIServices';
|
||||||
|
|
||||||
|
|
||||||
|
// Exporting the function as named export
|
||||||
|
// export const getByUsrGrpId = async (usrgrp) => {
|
||||||
|
// try {
|
||||||
|
// const response = await apiService.get(`/api1/getusracces1/${usrgrp}`);
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
// }
|
||||||
|
// console.log(response.data);
|
||||||
|
// return response.data;
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error fetching user groups:', error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const getByUsrGrpId = async (usrgrp) => {
|
||||||
|
if (!usrgrp) {
|
||||||
|
console.error("Error: usrgrp is not defined or is null.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await apiService.get(`/api1/getusracces1/${usrgrp}`);
|
||||||
|
if (response.status < 200 || response.status >= 300) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
console.log("Fetched data successfully:", response.data);
|
||||||
|
return response.data; // Return the data if successfully fetched
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching user groups:", error.message || error);
|
||||||
|
throw error; // Re-throw the error to handle it in the caller function
|
||||||
|
}
|
||||||
|
};
|
||||||
23
src/ApiServices/SytemparameterApi.js
Normal file
23
src/ApiServices/SytemparameterApi.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import apiService from '../APIRequestService/APIServices';
|
||||||
|
|
||||||
|
export const getSysParameter = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.get(`/sysparam/getSysParams/${id}`);
|
||||||
|
return response.data; // Return only the data part of the response
|
||||||
|
} catch (error) {
|
||||||
|
throw error; // Let the error be handled by the calling component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSysParameter = async (formdata) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.put(`/sysparam/updateSysParams/${1}`,formdata);
|
||||||
|
console.log("add response data ",response.data)
|
||||||
|
return response.data; // Return only the data part of the response
|
||||||
|
} catch (error) {
|
||||||
|
throw error; // Let the error be handled by the calling component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
48
src/ApiServices/Usermaintenanceapi.js
Normal file
48
src/ApiServices/Usermaintenanceapi.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import apiService from '../APIRequestService/APIServices';
|
||||||
|
|
||||||
|
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
|
export const getAllUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.get(`${API_BASE_URL}/api/getAllAppUser`);
|
||||||
|
console.log(response.data);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user groups:', error);
|
||||||
|
throw error; // Consider re-throwing the error for handling in components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUser = async (data) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.post(`${API_BASE_URL}/api/addOneAppUser`, data);
|
||||||
|
console.log(response.data);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateUser = async (userData) => {
|
||||||
|
try {
|
||||||
|
const { userId, ...data } = userData;
|
||||||
|
const response = await apiService.put(`${API_BASE_URL}/api/updateAppUser/${userId}`, data);
|
||||||
|
console.log(response.data);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUser = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.delete(`${API_BASE_URL}/api/deleteAppUser/${id}`);
|
||||||
|
console.log(response.data);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
153
src/ApiServices/menumaintananceapi.js
Normal file
153
src/ApiServices/menumaintananceapi.js
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import apiService from '../../src/APIRequestService/APIServices';
|
||||||
|
|
||||||
|
export const fetchMenuItems = async () => {
|
||||||
|
console.log("applying fetch menu items");
|
||||||
|
try {
|
||||||
|
const response = await apiService.get('/api1/submenu1');
|
||||||
|
console.log("response", response);
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.data || !Array.isArray(response.data)) {
|
||||||
|
throw new Error('Unexpected data format, expected an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Fetched Menu Items:', response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching menu items:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMenuItem = async (formData) => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
menuItemDesc: formData.menuItemDesc,
|
||||||
|
menuId: 0,
|
||||||
|
itemSeq: formData.itemSeq || 0,
|
||||||
|
moduleName: formData.moduleName || "",
|
||||||
|
main_menu_action_name: formData.main_menu_action_name || "#",
|
||||||
|
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
|
||||||
|
status: formData.status === "true"
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Final payload:", payload);
|
||||||
|
const response = await apiService.post('/api1/Sec_menuDet/', payload);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding menu item:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateMenuItem = async (id, formData) => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
menuItemId: Number(id),
|
||||||
|
menuItemDesc: formData.menuItemDesc,
|
||||||
|
menuId: formData.menuId || 0,
|
||||||
|
itemSeq: formData.itemSeq || 0,
|
||||||
|
moduleName: formData.moduleName || "",
|
||||||
|
main_menu_action_name: formData.main_menu_action_name || "#",
|
||||||
|
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
|
||||||
|
status: formData.status === "Enable" || formData.status === true
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Updating menu item #"+id, payload);
|
||||||
|
const response = await apiService.post('/api1/Sec_menuDet/', payload);
|
||||||
|
console.log("Update successful:", response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Update failed:', {
|
||||||
|
id: id,
|
||||||
|
error: error.response?.data || error.message
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteMenuItem = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.delete(`/api1/menu/${id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSubmenuItems = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.get(`/api1/submenu1/${id}`);
|
||||||
|
console.log("get submenu response: ", response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSubmenuItem = async (parentMenuId, formData) => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
menuItemDesc: formData.menuItemDesc,
|
||||||
|
menuId: Number(parentMenuId),
|
||||||
|
itemSeq: formData.itemSeq || 0,
|
||||||
|
moduleName: formData.moduleName || "",
|
||||||
|
main_menu_action_name: formData.main_menu_action_name || "#",
|
||||||
|
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
|
||||||
|
status: formData.status === "Enable"
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Adding submenu with payload:", payload);
|
||||||
|
const response = await apiService.post('/api1/Sec_menuDet/', payload);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding submenu:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSubmenuItem = async (submenuId, formData) => {
|
||||||
|
const payload = {
|
||||||
|
menuItemId: Number(submenuId),
|
||||||
|
menuItemDesc: formData.menuItemDesc,
|
||||||
|
menuId: Number(formData.menuId),
|
||||||
|
itemSeq: formData.itemSeq || 0,
|
||||||
|
moduleName: formData.moduleName || "",
|
||||||
|
main_menu_action_name: formData.main_menu_action_name || "#",
|
||||||
|
main_menu_icon_name: formData.main_menu_icon_name || "fa-circle",
|
||||||
|
status: formData.status === "Enable" || formData.status === true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("Updating submenu with payload:", payload);
|
||||||
|
const response = await apiService.post('/api1/Sec_menuDet/', payload);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating submenu:', {
|
||||||
|
error: error.response?.data || error.message,
|
||||||
|
payload: payload
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSubmenuItem = async (submenuId) => {
|
||||||
|
try {
|
||||||
|
console.log("Deleting submenu with ID:", submenuId);
|
||||||
|
const response = await apiService.post('/api1/Sec_menuDet/', {
|
||||||
|
menuItemId: Number(submenuId)
|
||||||
|
});
|
||||||
|
console.log("Delete successful:", response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting submenu:', {
|
||||||
|
status: error.response?.status,
|
||||||
|
error: error.response?.data || error.message,
|
||||||
|
submenuId: submenuId
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -46,4 +46,3 @@
|
|||||||
/* .login-container input[type="checkbox"] {
|
/* .login-container input[type="checkbox"] {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
|||||||
123
src/App.js
123
src/App.js
@@ -1,37 +1,114 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Routes, Route } from "react-router-dom";
|
import { SystemParameterProvider } from './context/SystemParameterContext';
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
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 Login from "./components/Login/Login";
|
||||||
import Dashboard from "./components/Dashboard/dashboard";
|
import Dashboard from "./components/Dashboard/dashboard";
|
||||||
import UserMaintance from "./components/Dashboard/UserMaintance";
|
import HomePage from "./components/Dashboard/HomePage";
|
||||||
import UserGroupMaintance from "./components/Dashboard/UserGroupMaintance/UserGroupMaintance";
|
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 CodeExtension from "./components/Dashboard/Codeextension";
|
||||||
import Extension from "./components/Dashboard/Extension";
|
|
||||||
import DynamicTable from "./components/Dashboard/Dynamictable";
|
import DynamicTable from "./components/Dashboard/Dynamictable";
|
||||||
import Form from "./components/Dashboard/Form";
|
import Report from "./components/Dashboard/reports/Report";
|
||||||
import ForgotPassword from "./components/Login/ForgotPassword";
|
import SequenceGenerator from "./components/Dashboard/document sequence/sequencegenerator";
|
||||||
import CreateAccount from "./components/Login/CreateAccount";
|
import About from "./components/Dashboard/dropdown/about";
|
||||||
import DashboardBuilder from "./components/Dashboard/DashboardBuilder";
|
import Profile from "./components/Dashboard/dropdown/profile";
|
||||||
|
import DashboardRunner from "./components/dashboardnew/dashboardrunner/dashboardrunner";
|
||||||
|
import DashboardRunnerAll from "./components/dashboardnew/dashboardrunner/dashboardrunnerall";
|
||||||
|
import DashboardNewAll from "./components/dashboardnew/dashboardbuildernewall";
|
||||||
|
import DashboardNewAdd from "./components/dashboardnew/dashboardadd/dashboardbuilderadd";
|
||||||
|
import DashboardNewEdit from "./components/dashboardnew/editdashboard/editformdashboard";
|
||||||
|
import EditNewDash from "./components/dashboardnew/editdashboard/editdashboard";
|
||||||
|
import SubMenuMaintenance from "./components/Dashboard/sub menu/submenumaintanence";
|
||||||
|
const theme = createTheme({
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#1976d2',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#dc004e',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<ThemeProvider theme={theme}>
|
||||||
<Router>
|
<CssBaseline />
|
||||||
|
<SystemParameterProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<ToastContainer
|
||||||
|
position="top-right"
|
||||||
|
autoClose={1500}
|
||||||
|
hideProgressBar={false}
|
||||||
|
newestOnTop
|
||||||
|
closeOnClick
|
||||||
|
rtl={false}
|
||||||
|
pauseOnFocusLoss
|
||||||
|
draggable
|
||||||
|
pauseOnHover
|
||||||
|
/>
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Login />} />
|
<Route path="/" element={<Login />} />
|
||||||
<Route path="/Dashboard" element={<Dashboard />} />
|
|
||||||
<Route path="/UserGroupMaintance" element={<UserGroupMaintance />} />
|
{/* Main dashboard route */}
|
||||||
<Route path="/Dashboard/UserMaintance" element={<UserMaintance />} />
|
<Route path="/dashboard" element={<Dashboard />}>
|
||||||
<Route path="/CodeExtension" element={<CodeExtension />} />
|
<Route index element={<HomePage />} />
|
||||||
<Route path="/Dashboard/DashboardBuilder" element={<DashboardBuilder />} />
|
|
||||||
<Route path="/Extension" element={<Extension />} />
|
{/* Setup section with all maintenance routes */}
|
||||||
<Route path="/Dynamictable" element={<DynamicTable />} />
|
<Route path="setup" element={<Setup />}>
|
||||||
<Route path="/Form" element={<Form />} />
|
<Route index element={<div>Select a setup option from the menu</div>} />
|
||||||
<Route path="/ForgotPassword" element={<ForgotPassword />} />
|
<Route path="user-maintenance" element={<UserMaintenance />} />
|
||||||
<Route path="/CreateAccount" element={<CreateAccount />} />
|
<Route path="user-group-maintenance" element={<UserGroupMaintenance />} />
|
||||||
|
<Route path="menu-maintenance" element={<MenuMaintenance />} />
|
||||||
|
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
|
||||||
|
|
||||||
|
<Route path="menu-access-control" element={<MenuAccessControl />} />
|
||||||
|
<Route path="system-parameters" element={<SystemParameters />} />
|
||||||
|
<Route path="access-type" element={<AccessType />} />
|
||||||
|
<Route path="api-registry" element={<ApiRegistry />} />
|
||||||
|
<Route path="token-registry" element={<TokenRegistry />} />
|
||||||
|
<Route path="document-sequence" element={<SequenceGenerator />} />
|
||||||
|
{/* Additional components */}
|
||||||
|
<Route path="code-extension" element={<CodeExtension />} />
|
||||||
|
<Route path="dynamic-table" element={<DynamicTable />} />
|
||||||
|
|
||||||
|
</Route>
|
||||||
|
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
|
||||||
|
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
|
||||||
|
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
|
||||||
|
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
|
||||||
|
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
|
||||||
|
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
|
||||||
|
|
||||||
|
<Route path="reports" element={<Report />} />
|
||||||
|
<Route path="about" element={<About />} />
|
||||||
|
<Route path="profile" element={<Profile />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</BrowserRouter>
|
||||||
</div>
|
</SystemParameterProvider>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
469
src/components/Dashboard/AccessType/AccessType.js
Normal file
469
src/components/Dashboard/AccessType/AccessType.js
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Checkbox,
|
||||||
|
Modal,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
TextField,
|
||||||
|
FormControlLabel,
|
||||||
|
Typography,
|
||||||
|
Pagination,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Box,
|
||||||
|
Divider
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Edit as EditIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Add as AddIcon,
|
||||||
|
Menu as MenuIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
Check as CheckIcon,
|
||||||
|
Book as BookIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
|
||||||
|
const api =process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
|
function AccessTypeManagement() {
|
||||||
|
const [accessTypes, setAccessTypes] = useState([]);
|
||||||
|
const [showAddEditModal, setShowAddEditModal] = useState(false);
|
||||||
|
const [currentAccessType, setCurrentAccessType] = useState({
|
||||||
|
typeId: "",
|
||||||
|
typeName: "",
|
||||||
|
description: "",
|
||||||
|
isActive: false
|
||||||
|
});
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||||
|
const [visibleColumns, setVisibleColumns] = useState({
|
||||||
|
typeId: true,
|
||||||
|
typeName: true,
|
||||||
|
description: true,
|
||||||
|
isActive: true,
|
||||||
|
actions: true
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
|
||||||
|
|
||||||
|
const openColumnsMenu = (event) => {
|
||||||
|
setColumnsAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeColumnsMenu = () => {
|
||||||
|
setColumnsAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRecordsMenu = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeRecordsMenu = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAccessTypes = async () => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const apiUrl = `${api}/token/access_type/Accesstype`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setAccessTypes(data);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching access types:", error);
|
||||||
|
toast.error("Failed to fetch access types");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAccessTypes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleColumn = (column) => {
|
||||||
|
setVisibleColumns((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[column]: !prev[column],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
const { name, value, checked, type } = event.target;
|
||||||
|
setCurrentAccessType((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === "checkbox" ? checked : value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const apiUrl = isEditing
|
||||||
|
? `${api}/access_type/Accesstype/${currentAccessType.id}`
|
||||||
|
: `${api}/access_type/Accesstype`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const method = isEditing ? "PUT" : "POST";
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(currentAccessType),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
setAccessTypes(accessTypes.map(type =>
|
||||||
|
type.id === currentAccessType.id ? data : type
|
||||||
|
));
|
||||||
|
toast.success("Access type updated successfully!");
|
||||||
|
} else {
|
||||||
|
setAccessTypes([...accessTypes, data]);
|
||||||
|
toast.success("Access type added successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowAddEditModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting access type:", error);
|
||||||
|
toast.error("Failed to submit access type");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const apiUrl = `${api}/access_type/Accesstype/${id}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccessTypes(accessTypes.filter(type => type.id !== id));
|
||||||
|
toast.success("Access type deleted successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting access type:", error);
|
||||||
|
toast.error("Failed to delete access type");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModal = (type = { typeId: "", typeName: "", description: "", isActive: false }) => {
|
||||||
|
setIsEditing(!!type.id);
|
||||||
|
setCurrentAccessType(type);
|
||||||
|
setShowAddEditModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setShowAddEditModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredAccessTypes = accessTypes.filter(item =>
|
||||||
|
item.typeName && item.typeName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const slicedAccessTypes = filteredAccessTypes.slice(
|
||||||
|
(currentPage - 1) * recordsPerPage,
|
||||||
|
currentPage * recordsPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(filteredAccessTypes.length / recordsPerPage);
|
||||||
|
|
||||||
|
const handlePageChange = (event, value) => {
|
||||||
|
setCurrentPage(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecordsPerPageChange = (number) => {
|
||||||
|
setRecordsPerPage(number);
|
||||||
|
setCurrentPage(1);
|
||||||
|
closeRecordsMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="xl" sx={{ mt: 4 }}>
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '200px' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3 }}>
|
||||||
|
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||||
|
Access Type
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
|
<TextField
|
||||||
|
placeholder="Search"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
sx: {
|
||||||
|
borderRadius: '10px',
|
||||||
|
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '528px',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{ flexGrow: 1, maxWidth: '528px' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<IconButton onClick={() => openModal()} sx={{ mr: 2, color: 'primary.main' }}>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={openColumnsMenu} sx={{ color: 'primary.main' }}>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer component={Paper} sx={{ boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', mb: 3 }}>
|
||||||
|
<Table>
|
||||||
|
<TableHead sx={{ backgroundColor: '#f8f9fa' }}>
|
||||||
|
<TableRow>
|
||||||
|
{Object.entries(visibleColumns).map(([key, visible]) =>
|
||||||
|
visible && (
|
||||||
|
<TableCell key={key} sx={{ fontWeight: 'bold' }}>
|
||||||
|
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
|
</TableCell>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{slicedAccessTypes.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={Object.keys(visibleColumns).filter(key => visibleColumns[key]).length} align="center">
|
||||||
|
No Data Available
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
slicedAccessTypes.map((type) => (
|
||||||
|
<TableRow key={type.id}>
|
||||||
|
{Object.entries(visibleColumns).map(([key, visible]) =>
|
||||||
|
visible && (
|
||||||
|
<TableCell key={key}>
|
||||||
|
{key === "actions" ? (
|
||||||
|
<Box>
|
||||||
|
<IconButton onClick={() => openModal(type)} sx={{ color: 'success.main', mr: 1 }}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleDelete(type.id)} sx={{ color: 'error.main' }}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
) : key === "isActive" ? (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
color: type.isActive ? 'success.main' : 'error.main',
|
||||||
|
backgroundColor: type.isActive ? 'rgba(0, 200, 83, 0.1)' : 'rgba(255, 0, 0, 0.1)',
|
||||||
|
px: 1,
|
||||||
|
py: 0.5,
|
||||||
|
borderRadius: 1,
|
||||||
|
display: 'inline-block'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{type.isActive ? "Active" : "Inactive"}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
type[key]
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={openColumnsMenu}
|
||||||
|
startIcon={<MenuIcon />}
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
>
|
||||||
|
Manage Columns
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={columnsAnchorEl}
|
||||||
|
open={Boolean(columnsAnchorEl)}
|
||||||
|
onClose={closeColumnsMenu}
|
||||||
|
>
|
||||||
|
{Object.keys(visibleColumns).map((column) => (
|
||||||
|
<MenuItem key={column} onClick={() => toggleColumn(column)}>
|
||||||
|
<Checkbox checked={visibleColumns[column]} />
|
||||||
|
<Typography>
|
||||||
|
{column.charAt(0).toUpperCase() + column.slice(1)}
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={openRecordsMenu}
|
||||||
|
startIcon={<BookIcon />}
|
||||||
|
>
|
||||||
|
Records
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={closeRecordsMenu}
|
||||||
|
>
|
||||||
|
{[5, 10, 20, 50].map((number) => (
|
||||||
|
<MenuItem
|
||||||
|
key={number}
|
||||||
|
onClick={() => handleRecordsPerPageChange(number)}
|
||||||
|
selected={recordsPerPage === number}
|
||||||
|
>
|
||||||
|
{number}
|
||||||
|
{recordsPerPage === number && <CheckIcon sx={{ ml: 1 }} />}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
count={totalPages}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
color="primary"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Add/Edit Modal */}
|
||||||
|
<Modal
|
||||||
|
open={showAddEditModal}
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="modal-modal-title"
|
||||||
|
aria-describedby="modal-modal-description"
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 400,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
borderRadius: 1
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
|
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||||
|
{isEditing ? "Edit Access Type" : "Add Access Type"}
|
||||||
|
</Typography>
|
||||||
|
<IconButton onClick={handleClose}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ mb: 3 }} />
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Type Name"
|
||||||
|
name="typeName"
|
||||||
|
value={currentAccessType.typeName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={currentAccessType.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
name="isActive"
|
||||||
|
checked={currentAccessType.isActive}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Active?"
|
||||||
|
sx={{ mb: 3 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
||||||
|
<Button variant="outlined" onClick={handleClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" type="submit">
|
||||||
|
{isEditing ? "Update" : "Add"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccessTypeManagement;
|
||||||
@@ -1,138 +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";
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
||||||
import { Box, Button } from "@mui/material";
|
'&:nth-of-type(odd)': {
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
backgroundColor: theme.palette.action.hover,
|
||||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
},
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.action.selected,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const api = process.env.REACT_APP_API_BASE_URL;
|
const APIRegistry = () => {
|
||||||
|
const [apiEntries, setApiEntries] = useState([]);
|
||||||
function CustomToolbar({ apiRef, handleModal }) {
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const handleGoToPage1 = () => {
|
const [currentApiEntry, setCurrentApiEntry] = useState({
|
||||||
if (apiRef.current) {
|
table_name: "",
|
||||||
apiRef.current.setPage(1);
|
isActive: false,
|
||||||
}
|
});
|
||||||
};
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
return (
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
<GridToolbarContainer className="flex justify-between p-2 bg-gray-200">
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
<Button
|
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||||
onClick={handleGoToPage1}
|
const [visibleColumns, setVisibleColumns] = useState({
|
||||||
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600"
|
id: true,
|
||||||
>
|
table_name: true,
|
||||||
Go to page 1
|
createdAt: true,
|
||||||
</Button>
|
updatedAt: true,
|
||||||
<Button
|
isActive: true,
|
||||||
onClick={handleModal}
|
actions: true,
|
||||||
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600"
|
});
|
||||||
>
|
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
|
||||||
Add item
|
const [recordsAnchorEl, setRecordsAnchorEl] = useState(null);
|
||||||
</Button>
|
const columnsMenuOpen = Boolean(columnsAnchorEl);
|
||||||
</GridToolbarContainer>
|
const recordsMenuOpen = Boolean(recordsAnchorEl);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ApiRegistery() {
|
|
||||||
const [menuItems, setMenuItems] = useState([]);
|
|
||||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const apiRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const loadData = async () => {
|
||||||
const token = localStorage.getItem("token"); // Get token from local storage
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
setLoading(true);
|
||||||
`${api}/Api_registery_header/Api_registery_header`,
|
const data = await fetchRegistry();
|
||||||
{
|
setApiEntries(data || []);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const data = await response.json();
|
|
||||||
setMenuItems(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
toast.error("Failed to fetch API entries");
|
||||||
|
console.error("Error loading data:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
loadData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleThreeDotsClick = (menuItemId) => {
|
const handleColumnsMenuClick = (event) => {
|
||||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
setColumnsAnchorEl(event.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const handleRecordsMenuClick = (event) => {
|
||||||
{
|
setRecordsAnchorEl(event.currentTarget);
|
||||||
field: "id",
|
};
|
||||||
headerName: "ID",
|
|
||||||
width: 300,
|
|
||||||
headerClassName: "custom-header",
|
|
||||||
cellClassName: "custom-cell",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "table_name",
|
|
||||||
headerName: "Table Name",
|
|
||||||
width: 350,
|
|
||||||
headerClassName: "custom-header",
|
|
||||||
cellClassName: "custom-cell",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "actions",
|
|
||||||
headerName: "Actions",
|
|
||||||
width: 150,
|
|
||||||
renderCell: ({ row }) => (
|
|
||||||
<div className="relative">
|
|
||||||
<div
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => handleThreeDotsClick(row.id)}
|
|
||||||
>
|
|
||||||
<BsThreeDotsVertical /> {/* Using react-icons */}
|
|
||||||
</div>
|
|
||||||
{selectedMenuItem === row.id && (
|
|
||||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
|
||||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="flex justify-center mt-5">
|
<Box display="flex" justifyContent="center" alignItems="center" height="80vh">
|
||||||
<Box className="w-full max-w-7xl">
|
<CircularProgress />
|
||||||
<div className="bg-white p-4 rounded shadow-md">
|
|
||||||
<h1 className="text-2xl font-bold mb-4 text-white bg-gray-400 p-3">
|
|
||||||
API Registry
|
|
||||||
</h1>
|
|
||||||
<DataGrid
|
|
||||||
rows={menuItems}
|
|
||||||
columns={columns}
|
|
||||||
components={{
|
|
||||||
Toolbar: () => (
|
|
||||||
<CustomToolbar
|
|
||||||
apiRef={apiRef}
|
|
||||||
handleModal={() => setIsModalOpen(true)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
pageSize={10}
|
|
||||||
onGridReady={(gridApi) => {
|
|
||||||
apiRef.current = gridApi;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApiRegistery;
|
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;
|
||||||
54
src/components/Dashboard/ApiRegistery/Apiregistryapi.js
Normal file
54
src/components/Dashboard/ApiRegistery/Apiregistryapi.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import apiService from '../../../APIRequestService/APIServices';
|
||||||
|
|
||||||
|
export const fetchRegistry = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.get('/Api_registery_header/Api_registery_header');
|
||||||
|
if (response.data && Array.isArray(response.data)) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in fetchRegistry:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addRegistry = async (payload) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.post('/Api_registery_header/Api_registery_header', {
|
||||||
|
table_name: payload.table_name,
|
||||||
|
createdAt: payload.createdAt || new Date().toISOString(),
|
||||||
|
updatedAt: payload.updatedAt || new Date().toISOString(),
|
||||||
|
isActive: payload.isActive || false
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in addRegistry:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRegistry = async (id, payload) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.put(`/Api_registery_header/Api_registery_header/${id}`, {
|
||||||
|
table_name: payload.table_name,
|
||||||
|
createdAt: payload.createdAt,
|
||||||
|
updatedAt: new Date().toISOString(), // Always update the updatedAt timestamp
|
||||||
|
isActive: payload.isActive
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in updateRegistry:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRegistry = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await apiService.delete(`/Api_registery_header/Api_registery_header/${id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in deleteRegistry:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
142
src/components/Dashboard/CardList.js
Normal file
142
src/components/Dashboard/CardList.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faUser, faUsers, faUtensils, faLock, faCogs, faKey } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { Card, CardContent, Typography, Box } from '@mui/material';
|
||||||
|
|
||||||
|
const CardItem = ({ title, content, icon, path }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
width: 300,
|
||||||
|
height: 150,
|
||||||
|
m: 2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
boxShadow: '10px 10px 10px 10px rgba(25,118,210,0.15)'
|
||||||
|
},
|
||||||
|
boxShadow: '10px 10px 10px 10px rgba(0,0,0,0.05)',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
onClick={() => navigate(path)}
|
||||||
|
>
|
||||||
|
<CardContent sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
padding: '16px !important'
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
backgroundColor: '#1976d2',
|
||||||
|
borderRadius: '50%',
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: 2,
|
||||||
|
color: 'white'
|
||||||
|
}}>
|
||||||
|
<FontAwesomeIcon icon={icon} style={{ fontSize: '1.2rem' }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" component="div" fontWeight="bold">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{content}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardList = () => {
|
||||||
|
const cards = [
|
||||||
|
{
|
||||||
|
title: "User Maintenance",
|
||||||
|
content: "Manage users",
|
||||||
|
icon: faUser,
|
||||||
|
path: "/dashboard/setup/user-maintenance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User Group Maintenance",
|
||||||
|
content: "Manage user groups",
|
||||||
|
icon: faUsers,
|
||||||
|
path: "/dashboard/setup/user-group-maintenance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Menu Maintenance",
|
||||||
|
content: "Manage menus",
|
||||||
|
icon: faUtensils,
|
||||||
|
path: "/dashboard/setup/menu-maintenance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Menu Access Control",
|
||||||
|
content: "Control menu access",
|
||||||
|
icon: faLock,
|
||||||
|
path: "/dashboard/setup/menu-access-control"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "System Parameters",
|
||||||
|
content: "Configure system parameters",
|
||||||
|
icon: faCogs,
|
||||||
|
path: "/dashboard/setup/system-parameters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Access Type",
|
||||||
|
content: "Manage access types",
|
||||||
|
icon: faKey,
|
||||||
|
path: "/dashboard/setup/access-type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Document Sequence",
|
||||||
|
content: "generate sequence",
|
||||||
|
icon: faCogs,
|
||||||
|
path: "/dashboard/setup/document-sequence"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Api Registry",
|
||||||
|
content: "Manage APIs",
|
||||||
|
icon: faUser,
|
||||||
|
path: "/dashboard/setup/api-registry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Token Registry",
|
||||||
|
content: "Manage tokens",
|
||||||
|
icon: faKey,
|
||||||
|
path: "/dashboard/setup/token-registry"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '2rem',
|
||||||
|
//backgroundColor: '#f5f5f5',
|
||||||
|
// minHeight: '100vh'
|
||||||
|
}}>
|
||||||
|
{cards.map((card, index) => (
|
||||||
|
<CardItem
|
||||||
|
key={index}
|
||||||
|
title={card.title}
|
||||||
|
content={card.content}
|
||||||
|
icon={card.icon}
|
||||||
|
path={card.path}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardList;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
Autocomplete,
|
Autocomplete,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||||
import { FaEllipsisV } from "react-icons/fa"; // Importing react-icons instead of Font Awesome
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||||
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
|
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Extension from "./Extension";
|
import Extension from "./Extension";
|
||||||
@@ -44,7 +45,7 @@ function CodeExtension() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
const token = localStorage.getItem("token"); // Get token from local storage
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/extension`, {
|
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/extension`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -104,7 +105,7 @@ function CodeExtension() {
|
|||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => handleThreeDotsClick(row.id)}
|
onClick={() => handleThreeDotsClick(row.id)}
|
||||||
>
|
>
|
||||||
<FaEllipsisV /> {/* Using react-icons instead of Font Awesome */}
|
<FontAwesomeIcon icon={faEllipsisV} />
|
||||||
</div>
|
</div>
|
||||||
{selectedMenuItem === row.id && (
|
{selectedMenuItem === row.id && (
|
||||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const DynamicTable = () => {
|
|||||||
}, [location.state]);
|
}, [location.state]);
|
||||||
|
|
||||||
const fetchForms = async () => {
|
const fetchForms = async () => {
|
||||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
const token = localStorage.getItem("token"); // Get token from local storage
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${api}/api/form_setup`, {
|
const response = await axios.get(`${api}/api/form_setup`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -55,7 +55,7 @@ const DynamicTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
const token = localStorage.getItem("token"); // Get token from local storage
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${api}/api/form_setup/${id}`, {
|
await axios.delete(`${api}/api/form_setup/${id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -69,7 +69,7 @@ const DynamicTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBuild = async (id) => {
|
const handleBuild = async (id) => {
|
||||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
const token = localStorage.getItem("token"); // Get token from local storage
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${api}/api/dynamic_form_build`,
|
`${api}/api/dynamic_form_build`,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const Extension = ({ onSubmit }) => {
|
|||||||
onSubmit(formData.dataType);
|
onSubmit(formData.dataType);
|
||||||
}
|
}
|
||||||
// Navigate to CodeExtension page with the form data
|
// Navigate to CodeExtension page with the form data
|
||||||
navigate('/CodeExtension', { state: { formData } });
|
navigate('/Codeextension', { state: { formData } });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
function Form() {
|
function DynamicForm() {
|
||||||
const [components, setComponents] = useState([
|
const [components, setComponents] = useState([
|
||||||
{
|
{
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
@@ -214,4 +214,4 @@ function Form() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Form;
|
export default DynamicForm;
|
||||||
|
|||||||
@@ -1,110 +1,34 @@
|
|||||||
// HomePage.js
|
// HomePage.js
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FaUsers, FaCog, FaChartBar, FaShieldAlt } from 'react-icons/fa';
|
import { BarChart } from '@mui/x-charts/BarChart'; // Import BarChart component
|
||||||
|
|
||||||
const StatCard = ({ icon: Icon, title, value, color }) => (
|
const Card = ({ index }) => {
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-all duration-200">
|
return (
|
||||||
<div className="flex items-center space-x-4">
|
<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">
|
||||||
<div className={`p-3 rounded-lg ${color}`}>
|
<h3 className="text-lg font-semibold mb-2">INDEX {index}</h3>
|
||||||
<Icon className="w-6 h-6 text-white" />
|
<p className="text-gray-600">{index}.</p>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-gray-500 text-sm font-medium">{title}</h3>
|
|
||||||
<p className="text-2xl font-bold text-gray-800">{value}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4">
|
||||||
<div className="flex items-center justify-between">
|
<h2 className="text-3xl font-semibold text-gray-700 mb-6">Welcome to the Dashboard!</h2>
|
||||||
<h1 className="text-3xl font-bold text-gray-800">Welcome Back!</h1>
|
<div className="flex flex-wrap justify-center">
|
||||||
<div className="text-sm text-gray-500">Last updated: {new Date().toLocaleDateString()}</div>
|
<Card index={1} />
|
||||||
|
<Card index={2} />
|
||||||
|
<Card index={3} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-full mt-8 flex justify-center">
|
||||||
{/* Stats Grid */}
|
{/* Add BarChart component */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<BarChart
|
||||||
<StatCard
|
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
||||||
icon={FaUsers}
|
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
|
||||||
title="Total Users"
|
width={700}
|
||||||
value="1,234"
|
height={400}
|
||||||
color="bg-gradient-to-r from-purple-500 to-indigo-500"
|
|
||||||
/>
|
/>
|
||||||
<StatCard
|
|
||||||
icon={FaCog}
|
|
||||||
title="Active Systems"
|
|
||||||
value="12"
|
|
||||||
color="bg-gradient-to-r from-blue-500 to-cyan-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
icon={FaChartBar}
|
|
||||||
title="Reports Generated"
|
|
||||||
value="456"
|
|
||||||
color="bg-gradient-to-r from-indigo-500 to-purple-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
icon={FaShieldAlt}
|
|
||||||
title="Security Score"
|
|
||||||
value="98%"
|
|
||||||
color="bg-gradient-to-r from-green-500 to-emerald-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Recent Activity */}
|
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">Recent Activity</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{[1, 2, 3].map((item) => (
|
|
||||||
<div key={item} className="flex items-center space-x-4 p-4 rounded-lg hover:bg-gray-50 transition-colors">
|
|
||||||
<div className="w-2 h-2 rounded-full bg-purple-500"></div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="text-gray-800">System update completed</p>
|
|
||||||
<p className="text-sm text-gray-500">2 hours ago</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick Actions */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl shadow-lg p-6 text-white">
|
|
||||||
<h2 className="text-xl font-semibold mb-4">Quick Actions</h2>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
|
|
||||||
Generate Report
|
|
||||||
</button>
|
|
||||||
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
|
|
||||||
Add New User
|
|
||||||
</button>
|
|
||||||
<button className="w-full bg-white/10 hover:bg-white/20 text-white font-medium py-2 px-4 rounded-lg transition-colors">
|
|
||||||
System Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gradient-to-br from-blue-500 to-cyan-600 rounded-xl shadow-lg p-6 text-white">
|
|
||||||
<h2 className="text-xl font-semibold mb-4">System Status</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span>CPU Usage</span>
|
|
||||||
<span className="font-medium">45%</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-white/20 rounded-full h-2">
|
|
||||||
<div className="bg-white h-2 rounded-full" style={{ width: '45%' }}></div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span>Memory Usage</span>
|
|
||||||
<span className="font-medium">62%</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-white/20 rounded-full h-2">
|
|
||||||
<div className="bg-white h-2 rounded-full" style={{ width: '62%' }}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,177 +1,642 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Box, Button } from "@mui/material";
|
import {
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
Box,
|
||||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
Button,
|
||||||
import "./MenuMaintance.css";
|
Container,
|
||||||
|
Paper,
|
||||||
const api = process.env.REACT_APP_API_BASE_URL;
|
Table,
|
||||||
|
TableBody,
|
||||||
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
|
TableCell,
|
||||||
const handleGoToPage1 = () => {
|
TableContainer,
|
||||||
if (apiRef.current) {
|
TableHead,
|
||||||
apiRef.current.setPage(1);
|
TableRow,
|
||||||
}
|
TextField,
|
||||||
};
|
Typography,
|
||||||
|
IconButton,
|
||||||
return (
|
Tooltip,
|
||||||
<GridToolbarContainer>
|
Menu,
|
||||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
MenuItem,
|
||||||
<Button onClick={handleModal}>+</Button>
|
Checkbox,
|
||||||
</GridToolbarContainer>
|
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() {
|
function MenuMaintenance() {
|
||||||
const [menuItems, setMenuItems] = useState([]);
|
const [menuItems, setMenuItems] = useState([]);
|
||||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
const [subMenuItems, setSubMenuItems] = useState([]);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [showAddEditPopup, setShowAddEditPopup] = useState(false);
|
||||||
const apiRef = useRef(null);
|
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(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = localStorage.getItem("authToken");
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${api}/api1/submenu1`, {
|
console.log("Fetching Menu Items...");
|
||||||
headers: {
|
const data = await fetchMenuItems();
|
||||||
Authorization: `Bearer ${token}`,
|
setMenuItems(data);
|
||||||
},
|
console.log("Fetched Menu Items:", data);
|
||||||
});
|
toast.success("Menu items fetched successfully!");
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
// Flatten the nested subMenus array
|
|
||||||
const flattenedData = data.flatMap((menuItem) => [
|
|
||||||
menuItem,
|
|
||||||
...menuItem.subMenus,
|
|
||||||
]);
|
|
||||||
// Set unique IDs for each menu item
|
|
||||||
const menuItemsWithIds = flattenedData.map((menuItem, index) => ({
|
|
||||||
...menuItem,
|
|
||||||
id: index + 1,
|
|
||||||
}));
|
|
||||||
setMenuItems(menuItemsWithIds);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Fetching error:", error);
|
||||||
|
setError(error.message);
|
||||||
|
toast.error("Error fetching menu items.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleThreeDotsClick = (menuItemId) => {
|
const toggleColumn = (column) => {
|
||||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
setVisibleColumns((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[column]: !prev[column],
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const openAddEditPopup = (menuItem = {}) => {
|
||||||
{
|
setIsEditing(!!menuItem.menuItemId);
|
||||||
field: "menuItemId",
|
setCurrentMenuItem(menuItem);
|
||||||
headerName: "Menu Item ID",
|
setParentMenuItemId(isSubMenu ? menuItem.menuItemId : null);
|
||||||
width: 200,
|
setShowAddEditPopup(true);
|
||||||
headerClassName: "custom-header",
|
};
|
||||||
cellClassName: "custom-cell",
|
|
||||||
},
|
const handleOpenModal = () => setShowModal(true);
|
||||||
{
|
const handleCloseModal = () => setShowModal(false);
|
||||||
field: "menuItemDesc",
|
|
||||||
headerName: "Menu Item Description",
|
const handleFileChange = (event) => {
|
||||||
width: 250,
|
const file = event.target.files[0];
|
||||||
headerClassName: "custom-header",
|
if (file) {
|
||||||
cellClassName: "custom-cell",
|
console.log("Selected file:", file.name);
|
||||||
},
|
}
|
||||||
{
|
};
|
||||||
field: "moduleName",
|
|
||||||
headerName: "Module Name",
|
const exportToExcel = () => {
|
||||||
width: 200,
|
const worksheet = XLSX.utils.json_to_sheet([]);
|
||||||
headerClassName: "custom-header",
|
const workbook = XLSX.utils.book_new();
|
||||||
cellClassName: "custom-cell",
|
XLSX.utils.book_append_sheet(workbook, worksheet, "UserDetails");
|
||||||
},
|
XLSX.writeFile(workbook, "MenuMaintenance.xlsx");
|
||||||
{
|
};
|
||||||
field: "main_menu_action_name",
|
|
||||||
headerName: "Main Menu Action",
|
const handleInputChange = (event) => {
|
||||||
width: 200,
|
const { name, value } = event.target;
|
||||||
headerClassName: "custom-header",
|
const processedValue = name === "status" ? value === "true" : value;
|
||||||
cellClassName: "custom-cell",
|
setCurrentMenuItem((prev) => ({ ...prev, [name]: processedValue }));
|
||||||
},
|
};
|
||||||
{
|
|
||||||
field: "main_menu_icon_name",
|
const handleSearch = (query) => {
|
||||||
headerName: "Main Menu Icon",
|
setSearchQuery(query);
|
||||||
width: 200,
|
};
|
||||||
headerClassName: "custom-header",
|
|
||||||
cellClassName: "custom-cell",
|
const handleSubmit = async (event) => {
|
||||||
},
|
event.preventDefault();
|
||||||
{
|
try {
|
||||||
field: "actions",
|
if (isEditing) {
|
||||||
headerName: "Actions",
|
const updatedItem = await updateMenuItem(currentMenuItem.menuItemId, currentMenuItem);
|
||||||
width: 150,
|
setMenuItems(menuItems.map(item =>
|
||||||
renderCell: ({ row }) => (
|
item.menuItemId === currentMenuItem.menuItemId ? updatedItem : item
|
||||||
<div className="relative">
|
));
|
||||||
<div
|
toast.success("Menu item updated successfully!");
|
||||||
className="three-dots"
|
} else {
|
||||||
onClick={() => handleThreeDotsClick(row.menuItemId)}
|
const addedItem = await addMenuItem(currentMenuItem);
|
||||||
>
|
setMenuItems([...menuItems, addedItem]);
|
||||||
<BsThreeDotsVertical
|
toast.success("Menu item added successfully!");
|
||||||
className="cursor-pointer text-gray-800 hover:text-gray-600"
|
}
|
||||||
/>
|
setShowAddEditPopup(false);
|
||||||
</div>
|
} catch (error) {
|
||||||
{selectedMenuItem === row.menuItemId && (
|
toast.error(`Operation failed: ${error.message}`);
|
||||||
<div className="absolute bg-white border border-gray-200 shadow-lg p-4 mt-2 rounded-lg">
|
console.error("Submit error:", error);
|
||||||
{/* Implement your actions buttons here */}
|
}
|
||||||
</div>
|
};
|
||||||
)}
|
|
||||||
</div>
|
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 (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
<Box sx={{ mt: -2 }}>
|
||||||
<div className="text-3xl text-center text-black mb-3 bg-slate-400">
|
{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
|
Menu Maintenance
|
||||||
</div>
|
</Typography>
|
||||||
<Box
|
</Box>
|
||||||
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2"
|
|
||||||
sx={{ height: 500, width: "100%" }}
|
<Grid container spacing={2} alignItems="center" my={3}>
|
||||||
>
|
<Grid item xs={12} md={8} lg={6}>
|
||||||
<DataGrid
|
<TextField
|
||||||
rows={menuItems}
|
fullWidth
|
||||||
columns={columns}
|
placeholder="Search"
|
||||||
components={{
|
value={searchQuery}
|
||||||
Toolbar: () => (
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
<CustomToolbar
|
InputProps={{
|
||||||
apiRef={apiRef}
|
startAdornment: (
|
||||||
handleThreeDotsClick={handleThreeDotsClick}
|
<InputAdornment position="start">
|
||||||
handleModal={() => setIsModalOpen(true)}
|
<SearchIcon sx={{ color: '#fff' }} />
|
||||||
/>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
}}
|
sx: {
|
||||||
pageSize={10}
|
backgroundColor: '#fff',
|
||||||
onGridReady={(gridApi) => {
|
borderRadius: '10px',
|
||||||
apiRef.current = gridApi;
|
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
border: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiDataGrid-columnHeaders": {
|
maxWidth: 528
|
||||||
backgroundColor: "rgba(107, 114, 128, 0.5)", // Tailwind CSS bg-gray-400 with opacity
|
|
||||||
},
|
|
||||||
"& .MuiDataGrid-columnHeaderTitle": {
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
className="border border-gray-200 shadow-lg rounded-lg bg-gray-400"
|
/>
|
||||||
|
</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>
|
</Box>
|
||||||
{/* Your modals and other components */}
|
|
||||||
{isModalOpen && (
|
<Modal
|
||||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
open={showAddEditPopup}
|
||||||
<div className="bg-white p-8 rounded-lg shadow-lg">
|
onClose={handleClose}
|
||||||
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
|
aria-labelledby="modal-modal-title"
|
||||||
{/* Modal content here */}
|
aria-describedby="modal-modal-description"
|
||||||
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
|
>
|
||||||
</div>
|
<Box sx={{
|
||||||
</div>
|
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,21 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FaUser, FaUsers, FaUtensils, FaLock, FaCogs, FaKey } from 'react-icons/fa';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
import UserMaintance from './UserMaintance';
|
import CardList from './CardList'; // Import the CardList component
|
||||||
import UserGroupMaintance from './UserGroupMaintance/UserGroupMaintance';
|
|
||||||
import MenuMaintance from './MenuMaintance/MenuMaintance';
|
|
||||||
import MenuAccessControl from './MenuAccessControl/MenuAccessControl';
|
|
||||||
import SystemParameters from './SystemParameters/SystemParameters';
|
|
||||||
import ApiRegistery from './ApiRegistery/ApiRegistery';
|
|
||||||
import TokenRegistery from './TokenRegistery/TokenRegistery';
|
|
||||||
import Codeextension from './Codeextension.js';
|
|
||||||
import DynamicTable from './Dynamictable.js';
|
|
||||||
|
|
||||||
const Card = ({ title, content, icon: Icon, onClick }) => (
|
const Setup = () => {
|
||||||
<div onClick={onClick} className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
|
const navigate = useNavigate();
|
||||||
<Icon className="text-4xl text-gray-800 mb-4" />
|
|
||||||
<h3 className="text-lg font-semibold">{title}</h3>
|
|
||||||
<p className="text-gray-600">{content}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const CardList = () => {
|
|
||||||
const [showUserMaintance, setShowUserMaintance] = useState(false);
|
|
||||||
const [showUserGroupMaintance, setShowUserGroupMaintance] = useState(false);
|
|
||||||
const [showMenuMaintance, setShowMenuMaintance] = useState(false);
|
|
||||||
const [showMenuAccessControl, setShowMenuAccessControl] = useState(false);
|
|
||||||
const [showSystemParameters, setShowSystemParameters] = useState(false);
|
|
||||||
// const [showAccessType, setShowAccessType] = useState(false);
|
|
||||||
const [showApiRegistery, setShowApiRegistery] = useState(false);
|
|
||||||
const [showTokenRegistery, setShowTokenRegistery] = useState(false);
|
|
||||||
const [showCodeExtension, setShowCodeExtension] = useState(false);
|
|
||||||
const [showDynamicTable, setShowDynamicTable] = useState(false);
|
|
||||||
|
|
||||||
const handleCardClick = (menuItemDesc) => {
|
|
||||||
setShowUserMaintance(menuItemDesc === 'User Maintance');
|
|
||||||
setShowUserGroupMaintance(menuItemDesc === 'User Group Maintance');
|
|
||||||
setShowMenuMaintance(menuItemDesc === 'Menu Maintance');
|
|
||||||
setShowMenuAccessControl(menuItemDesc === 'Menu Access Control');
|
|
||||||
setShowSystemParameters(menuItemDesc === 'System Parameters');
|
|
||||||
// setShowAccessType(menuItemDesc === 'Access Type');
|
|
||||||
setShowApiRegistery(menuItemDesc === 'Api Registery');
|
|
||||||
setShowTokenRegistery(menuItemDesc === 'Token Registery');
|
|
||||||
setShowCodeExtension(menuItemDesc === 'Code Extension');
|
|
||||||
setShowDynamicTable(menuItemDesc === 'Dynamic Table');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div style={{ padding: '20px' }}>
|
||||||
{!showUserMaintance && !showUserGroupMaintance && !showMenuMaintance && !showMenuAccessControl && !showSystemParameters && !showApiRegistery && !showTokenRegistery && !showCodeExtension && !showDynamicTable && (
|
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-100 p-4">
|
{/* Show CardList when at /dashboard/setup */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
{window.location.pathname === '/dashboard/setup' ? (
|
||||||
<Card title="User Maintance" content="Manage users" icon={FaUser} onClick={() => handleCardClick('User Maintance')} />
|
<CardList />
|
||||||
<Card title="User Group Maintance" content="Manage user groups" icon={FaUsers} onClick={() => handleCardClick('User Group Maintance')} />
|
) : (
|
||||||
<Card title="Menu Maintance" content="Manage menus" icon={FaUtensils} onClick={() => handleCardClick('Menu Maintance')} />
|
<Outlet />
|
||||||
<Card title="Menu Access Control" content="Control menu access" icon={FaLock} onClick={() => handleCardClick('Menu Access Control')} />
|
|
||||||
<Card title="System Parameters" content="Configure system parameters" icon={FaCogs} onClick={() => handleCardClick('System Parameters')} />
|
|
||||||
{/* <Card title="Access Type" content="Manage access types" icon={FaKey} onClick={() => handleCardClick('Access Type')} /> */}
|
|
||||||
<Card title="Api Registery" content="Manage APIs" icon={FaUser} onClick={() => handleCardClick('Api Registery')} />
|
|
||||||
<Card title="Token Registery" content="Manage tokens" icon={FaKey} onClick={() => handleCardClick('Token Registery')} />
|
|
||||||
<Card title="Code Extension" content="Extend code functionalities" icon={FaLock} onClick={() => handleCardClick('Code Extension')} />
|
|
||||||
<Card title="Dynamic Table" content="Dynamic data tables" icon={FaKey} onClick={() => handleCardClick('Dynamic Table')} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{showUserMaintance && <UserMaintance />}
|
</div>
|
||||||
{showUserGroupMaintance && <UserGroupMaintance />}
|
|
||||||
{showMenuMaintance && <MenuMaintance />}
|
|
||||||
{showMenuAccessControl && <MenuAccessControl />}
|
|
||||||
{showSystemParameters && <SystemParameters />}
|
|
||||||
{/* {showAccessType && <AccessType />} */}
|
|
||||||
{showApiRegistery && <ApiRegistery />}
|
|
||||||
{showTokenRegistery && <TokenRegistery />}
|
|
||||||
{showCodeExtension && <Codeextension />}
|
|
||||||
{showDynamicTable && <DynamicTable />}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardList;
|
export default Setup;
|
||||||
@@ -1,35 +1,59 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Button, Container, Grid, TextField } from "@mui/material";
|
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 SystemParameterForm = () => {
|
||||||
|
const { systemParameters, loading: contextLoading } = useSystemParameters();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
schedulerTimer: "",
|
schedulerTime: "",
|
||||||
leaseTaxCode: "",
|
leaseTaxCode: "",
|
||||||
vesselConfirmationProcessLimit: "",
|
vesselConfProcessLimit: "",
|
||||||
rowToDisplay: "",
|
rowToDisplay: "",
|
||||||
linkToDisplay: "",
|
linkToDisplay: "",
|
||||||
rowToAdd: "",
|
rowToAdd: "",
|
||||||
lovRowToDisplay: "",
|
lovRowToDisplay: "",
|
||||||
lovLinkToDisplay: "",
|
lovLinkToDisplay: "",
|
||||||
oldServerName: "",
|
oidserverName: "",
|
||||||
oldBase: "",
|
oidBase: "",
|
||||||
oldAdminUser: "",
|
oidAdminUser: "",
|
||||||
oldServerPort: "",
|
oidServerPort: "",
|
||||||
userDefaultGroup: "",
|
userDefaultGroup: "",
|
||||||
defaultDepartment: "",
|
defaultDepartment: "",
|
||||||
defaultPosition: "",
|
defaultPosition: "",
|
||||||
singleCharge: "",
|
singleCharge: "",
|
||||||
firstDayOfWeek: "",
|
firstDayOftheWeek: "",
|
||||||
hourPerShift: "",
|
hourPerShift: "",
|
||||||
cnBillingFrequency: "",
|
cnBillingFrequency: "",
|
||||||
billingDepartmentCode: "",
|
billingDepartmentCode: "",
|
||||||
basePriceList: "",
|
basePriceList: "",
|
||||||
nonContainerServiceOrderAutoApprovalDeptCode: "",
|
nonContainerServiceOrder: "",
|
||||||
ediMAESchedulerOnOff: "",
|
ediMaeSchedulerONOFF: "",
|
||||||
ediSchedulerOnOff: "",
|
ediSchedulerONOFF: "",
|
||||||
companyDisplayName: "",
|
upload_Logo: null,
|
||||||
|
company_Display_Name: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (systemParameters) {
|
||||||
|
setFormData((prevForm) => ({
|
||||||
|
...prevForm,
|
||||||
|
...systemParameters,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [systemParameters]);
|
||||||
|
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
setFormData((prevState) => ({
|
setFormData((prevState) => ({
|
||||||
@@ -41,86 +65,153 @@ const SystemParameterForm = () => {
|
|||||||
const handleFileChange = (event) => {
|
const handleFileChange = (event) => {
|
||||||
setFormData((prevState) => ({
|
setFormData((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
logo: event.target.files[0],
|
upload_Logo: event.target.files[0],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
alert("Form submitted successfully!");
|
|
||||||
|
try {
|
||||||
console.log("Form Data:", formData);
|
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 = () => {
|
const handleClear = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
schedulerTimer: "",
|
schedulerTime: "",
|
||||||
leaseTaxCode: "",
|
leaseTaxCode: "",
|
||||||
vesselConfirmationProcessLimit: "",
|
vesselConfProcessLimit: "",
|
||||||
rowToDisplay: "",
|
rowToDisplay: "",
|
||||||
linkToDisplay: "",
|
linkToDisplay: "",
|
||||||
rowToAdd: "",
|
rowToAdd: "",
|
||||||
lovRowToDisplay: "",
|
lovRowToDisplay: "",
|
||||||
lovLinkToDisplay: "",
|
lovLinkToDisplay: "",
|
||||||
oldServerName: "",
|
oidserverName: "",
|
||||||
oldBase: "",
|
oidBase: "",
|
||||||
oldAdminUser: "",
|
oidAdminUser: "",
|
||||||
oldServerPort: "",
|
oidServerPort: "",
|
||||||
userDefaultGroup: "",
|
userDefaultGroup: "",
|
||||||
defaultDepartment: "",
|
defaultDepartment: "",
|
||||||
defaultPosition: "",
|
defaultPosition: "",
|
||||||
singleCharge: "",
|
singleCharge: "",
|
||||||
firstDayOfWeek: "",
|
firstDayOftheWeek: "",
|
||||||
hourPerShift: "",
|
hourPerShift: "",
|
||||||
cnBillingFrequency: "",
|
cnBillingFrequency: "",
|
||||||
billingDepartmentCode: "",
|
billingDepartmentCode: "",
|
||||||
basePriceList: "",
|
basePriceList: "",
|
||||||
nonContainerServiceOrderAutoApprovalDeptCode: "",
|
nonContainerServiceOrder: "",
|
||||||
ediMAESchedulerOnOff: "",
|
ediMaeSchedulerONOFF: "",
|
||||||
ediSchedulerOnOff: "",
|
ediSchedulerONOFF: "",
|
||||||
companyDisplayName: "",
|
upload_Logo: null,
|
||||||
|
company_Display_Name: "",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const formatLabel = (key) => {
|
||||||
<Container className="mt-5">
|
return key
|
||||||
<p className="bg-gray-400 text-center text-white text-3xl m-3 p-2">
|
|
||||||
System Parameters
|
|
||||||
</p>
|
|
||||||
<form onSubmit={handleSubmit} className="m-5 p-5">
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{Object.keys(formData).map((key, index) => (
|
|
||||||
<Grid item xs={12} sm={6} key={index}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label={key
|
|
||||||
.split(/(?=[A-Z])/)
|
.split(/(?=[A-Z])/)
|
||||||
.join(" ")
|
.join(" ")
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase())}
|
.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}
|
name={key}
|
||||||
value={formData[key]}
|
value={formData[key]}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
</React.Fragment>
|
||||||
<Grid item xs={12}>
|
) : (
|
||||||
<input type="file" onChange={handleFileChange} />
|
<React.Fragment key={index}>
|
||||||
|
<Grid item xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Typography>Upload Logo</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
InputProps={{
|
||||||
|
inputProps: {
|
||||||
|
accept: "image/*"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<div style={{ textAlign: "end", marginTop: 20 }}>
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 4 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{ marginRight: 10 }}
|
sx={{ mr: 2 }}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" color="primary" onClick={handleClear}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleClear}
|
||||||
|
>
|
||||||
Clear
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Box>
|
||||||
</form>
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
427
src/components/Dashboard/Test/Apitest.js
Normal file
427
src/components/Dashboard/Test/Apitest.js
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Snackbar,
|
||||||
|
Alert,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TablePagination,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment
|
||||||
|
} from "@mui/material";
|
||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
|
||||||
|
const API_URL = "http://34.198.218.30:30179/entityBuilder/Gaurav_testing";
|
||||||
|
const token = localStorage.getItem("authToken");
|
||||||
|
// Custom styles using makeStyles
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
tableHeader: {
|
||||||
|
backgroundColor: "#000000",
|
||||||
|
color: "#ffffff",
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
tableContainer: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
dialogContent: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
},
|
||||||
|
formControl: {
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const EntityTable = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [filteredData, setFilteredData] = useState([]);
|
||||||
|
const [newEntity, setNewEntity] = useState({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
mobno: "",
|
||||||
|
address: "",
|
||||||
|
pincode: "",
|
||||||
|
});
|
||||||
|
const [editEntity, setEditEntity] = useState(null);
|
||||||
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
const [deleteEntityId, setDeleteEntityId] = useState(null);
|
||||||
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
|
const [itemsPerPage] = useState(5); // Adjust this value as needed
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [openSnackbar, setOpenSnackbar] = useState(false);
|
||||||
|
const [snackbarMessage, setSnackbarMessage] = useState("");
|
||||||
|
const [snackbarSeverity, setSnackbarSeverity] = useState("success");
|
||||||
|
|
||||||
|
const handlePageChange = (event, newPage) => {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleSearch();
|
||||||
|
}, [searchQuery, data]);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(API_URL, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
setData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`${API_URL}/${deleteEntityId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
fetchData();
|
||||||
|
setSnackbarMessage("Successfully deleted!");
|
||||||
|
setSnackbarSeverity("success");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
setShowDeleteModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting data:", error);
|
||||||
|
setSnackbarMessage("Failed to delete!");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = async () => {
|
||||||
|
try {
|
||||||
|
await axios.post(API_URL, newEntity, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
fetchData();
|
||||||
|
setNewEntity({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
mobno: "",
|
||||||
|
address: "",
|
||||||
|
pincode: "",
|
||||||
|
});
|
||||||
|
setShowAddModal(false);
|
||||||
|
setSnackbarMessage("Successfully added!");
|
||||||
|
setSnackbarSeverity("success");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding data:", error);
|
||||||
|
setSnackbarMessage("Failed to add!");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setNewEntity({ ...newEntity, [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setEditEntity({ ...editEntity, [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (entity) => {
|
||||||
|
setEditEntity(entity);
|
||||||
|
setShowEditModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
try {
|
||||||
|
await axios.put(`${API_URL}/${editEntity.id}`, editEntity, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
fetchData();
|
||||||
|
setShowEditModal(false);
|
||||||
|
setSnackbarMessage("Successfully updated!");
|
||||||
|
setSnackbarSeverity("success");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating data:", error);
|
||||||
|
setSnackbarMessage("Failed to update!");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setOpenSnackbar(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
const filtered = data.filter(
|
||||||
|
(entity) =>
|
||||||
|
entity.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
entity.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
entity.mobno.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
entity.address.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
entity.pincode.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilteredData(filtered);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mt-5">
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Entity Table
|
||||||
|
</Typography>
|
||||||
|
<div className={classes.searchContainer}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => setShowAddModal(true)}
|
||||||
|
className={classes.button}
|
||||||
|
>
|
||||||
|
Add Entity
|
||||||
|
</Button>
|
||||||
|
<TextField
|
||||||
|
className={classes.searchInput}
|
||||||
|
label="Search"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<TableContainer component={Paper} className={classes.tableContainer}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow className={classes.tableHeader}>
|
||||||
|
<TableCell>ID</TableCell>
|
||||||
|
<TableCell>Name</TableCell>
|
||||||
|
<TableCell>Email</TableCell>
|
||||||
|
<TableCell>Mobile No</TableCell>
|
||||||
|
<TableCell>Address</TableCell>
|
||||||
|
<TableCell>Pincode</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{filteredData.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage).map((entity) => (
|
||||||
|
<TableRow key={entity.id}>
|
||||||
|
<TableCell>{entity.id}</TableCell>
|
||||||
|
<TableCell>{entity.name}</TableCell>
|
||||||
|
<TableCell>{entity.email}</TableCell>
|
||||||
|
<TableCell>{entity.mobno}</TableCell>
|
||||||
|
<TableCell>{entity.address}</TableCell>
|
||||||
|
<TableCell>{entity.pincode}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="warning"
|
||||||
|
size="small"
|
||||||
|
className={classes.button}
|
||||||
|
onClick={() => handleEdit(entity)}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
className={classes.button}
|
||||||
|
onClick={() => {
|
||||||
|
setDeleteEntityId(entity.id);
|
||||||
|
setShowDeleteModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25]}
|
||||||
|
component="div"
|
||||||
|
count={filteredData.length}
|
||||||
|
rowsPerPage={itemsPerPage}
|
||||||
|
page={currentPage}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
<Dialog open={showEditModal} onClose={() => setShowEditModal(false)}>
|
||||||
|
<DialogTitle>Edit Entity</DialogTitle>
|
||||||
|
<DialogContent className={classes.dialogContent}>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="name"
|
||||||
|
value={editEntity?.name || ""}
|
||||||
|
onChange={handleEditChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Email"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="email"
|
||||||
|
value={editEntity?.email || ""}
|
||||||
|
onChange={handleEditChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Mobile No"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="mobno"
|
||||||
|
value={editEntity?.mobno || ""}
|
||||||
|
onChange={handleEditChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Address"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="address"
|
||||||
|
value={editEntity?.address || ""}
|
||||||
|
onChange={handleEditChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Pincode"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="pincode"
|
||||||
|
value={editEntity?.pincode || ""}
|
||||||
|
onChange={handleEditChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowEditModal(false)} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleUpdate} color="primary">
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog open={showAddModal} onClose={() => setShowAddModal(false)}>
|
||||||
|
<DialogTitle>Add Entity</DialogTitle>
|
||||||
|
<DialogContent className={classes.dialogContent}>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="name"
|
||||||
|
value={newEntity.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Email"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="email"
|
||||||
|
value={newEntity.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Mobile No"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="mobno"
|
||||||
|
value={newEntity.mobno}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Address"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="address"
|
||||||
|
value={newEntity.address}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Pincode"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
name="pincode"
|
||||||
|
value={newEntity.pincode}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={classes.formControl}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowAddModal(false)} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAdd} color="primary">
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog open={showDeleteModal} onClose={() => setShowDeleteModal(false)}>
|
||||||
|
<DialogTitle>Confirm Delete</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>Are you sure you want to delete this entity?</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowDeleteModal(false)} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleDelete} color="primary">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Snackbar
|
||||||
|
open={openSnackbar}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={() => setOpenSnackbar(false)}
|
||||||
|
>
|
||||||
|
<Alert onClose={() => setOpenSnackbar(false)} severity={snackbarSeverity}>
|
||||||
|
{snackbarMessage}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityTable;
|
||||||
@@ -1,145 +1,746 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Box, Button } from "@mui/material";
|
import {
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
Button,
|
||||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
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';
|
||||||
|
|
||||||
const api = process.env.REACT_APP_API_BASE_URL;
|
function TOKENRegistry() {
|
||||||
|
const [tokens, setTokens] = useState([]);
|
||||||
function CustomToolbar({ apiRef, handleModal }) {
|
const [showAddEditModal, setShowAddEditModal] = useState(false);
|
||||||
const handleGoToPage1 = () => {
|
const [showGenerateTokenModal, setShowGenerateTokenModal] = useState(false);
|
||||||
if (apiRef.current) {
|
const [newTokenName, setNewTokenName] = useState("");
|
||||||
apiRef.current.setPage(1);
|
const [generatedToken, setGeneratedToken] = useState("");
|
||||||
}
|
const [currentToken, setCurrentToken] = useState({
|
||||||
};
|
id: "",
|
||||||
|
tokenName: "",
|
||||||
return (
|
tokenValue: "",
|
||||||
<GridToolbarContainer className="flex flex-wrap justify-between p-2 bg-gray-100 border-b border-gray-200">
|
isActive: true,
|
||||||
<Button
|
scopes: []
|
||||||
onClick={handleGoToPage1}
|
});
|
||||||
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 m-1"
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
>
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
Go to page 1
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
</Button>
|
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||||
<Button
|
const [visibleColumns, setVisibleColumns] = useState({
|
||||||
onClick={handleModal}
|
id: true,
|
||||||
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600 m-1"
|
tokenName: true,
|
||||||
>
|
tokenValue: true,
|
||||||
+
|
scopes: true,
|
||||||
</Button>
|
isActive: true,
|
||||||
</GridToolbarContainer>
|
actions: true
|
||||||
);
|
});
|
||||||
}
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selectedScope, setSelectedScope] = useState("");
|
||||||
function TokenRegistry() {
|
const [selectedScopes, setSelectedScopes] = useState([]);
|
||||||
const [menuItems, setMenuItems] = useState([]);
|
const [availableScopes] = useState([
|
||||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
{ value: 'read', label: 'Read Access' },
|
||||||
const [, setIsModalOpen] = useState(false);
|
{ value: 'write', label: 'Write Access' },
|
||||||
const apiRef = useRef(null);
|
{ 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(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
fetchTokens();
|
||||||
const token = localStorage.getItem("token"); // Get token from local storage
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${api}/apiregistery/getall`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const data = await response.json();
|
|
||||||
setMenuItems(data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching data:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleThreeDotsClick = (menuItemId) => {
|
const fetchTokens = async () => {
|
||||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await tokenRegistryAPI.fetchAllTokens();
|
||||||
|
setTokens(data);
|
||||||
|
} catch (error) {
|
||||||
|
handleApiError(error, "fetch tokens");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const handleApiError = (error, action) => {
|
||||||
{
|
if (error.message === 'Unauthorized') {
|
||||||
field: "table_id",
|
toast.error("Your session has expired. Please log in again.");
|
||||||
headerName: "Table ID",
|
} else {
|
||||||
width: 200,
|
toast.error(`Failed to ${action}`);
|
||||||
headerClassName: "custom-header",
|
console.error(`Error ${action}:`, error);
|
||||||
cellClassName: "custom-cell",
|
}
|
||||||
},
|
};
|
||||||
{
|
|
||||||
field: "token_name",
|
const toggleColumn = (column) => {
|
||||||
headerName: "Token Name",
|
setVisibleColumns(prev => ({
|
||||||
width: 250,
|
...prev,
|
||||||
headerClassName: "custom-header",
|
[column]: !prev[column],
|
||||||
cellClassName: "custom-cell",
|
}));
|
||||||
},
|
};
|
||||||
{
|
|
||||||
field: "token",
|
const handleInputChange = (event) => {
|
||||||
headerName: "Token",
|
const { name, value, type, checked } = event.target;
|
||||||
width: 200,
|
setCurrentToken(prev => ({
|
||||||
headerClassName: "custom-header",
|
...prev,
|
||||||
cellClassName: "custom-cell",
|
[name]: type === "checkbox" ? checked : value
|
||||||
},
|
}));
|
||||||
{
|
};
|
||||||
field: "actions",
|
|
||||||
headerName: "Actions",
|
const handleSearch = (query) => {
|
||||||
width: 150,
|
setSearchQuery(query);
|
||||||
renderCell: ({ row }) => (
|
setCurrentPage(1);
|
||||||
<div className="relative">
|
};
|
||||||
<div
|
|
||||||
className="cursor-pointer"
|
const handleClose = () => {
|
||||||
onClick={() => handleThreeDotsClick(row.id)}
|
setShowAddEditModal(false);
|
||||||
>
|
setShowGenerateTokenModal(false);
|
||||||
<BsThreeDotsVertical /> {/* Updated icon from react-icons */}
|
setGeneratedToken("");
|
||||||
</div>
|
setNewTokenName("");
|
||||||
{selectedMenuItem === row.id && (
|
setSelectedScopes([]);
|
||||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
};
|
||||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
|
||||||
Edit
|
const handleRecordsPerPageChange = (number) => {
|
||||||
</button>
|
setRecordsPerPage(number);
|
||||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
setCurrentPage(1);
|
||||||
Delete
|
setAnchorEl(null);
|
||||||
</button>
|
};
|
||||||
</div>
|
|
||||||
)}
|
const filteredTokens = tokens.filter(
|
||||||
</div>
|
(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 (
|
return (
|
||||||
<div className="flex justify-center mt-5 px-2 md:px-0">
|
<Box sx={{ marginTop: "1rem", padding: 2 }}>
|
||||||
<Box className="w-full max-w-7xl bg-gray-50 p-4 md:p-6 rounded shadow-md">
|
{loading ? (
|
||||||
<p className="text-2xl md:text-3xl text-center text-white bg-gray-400 mb-4 p-3">
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
|
||||||
Token Registry
|
<CircularProgress />
|
||||||
</p>
|
</Box>
|
||||||
<div className="bg-white p-2 md:p-4 rounded shadow-md">
|
) : (
|
||||||
<DataGrid
|
<Box>
|
||||||
rows={menuItems}
|
{/* Token Generation Card */}
|
||||||
columns={columns}
|
<Card sx={{ mb: 4, boxShadow: 3 }}>
|
||||||
components={{
|
<CardHeader
|
||||||
Toolbar: () => (
|
title="Token Management"
|
||||||
<CustomToolbar
|
action={
|
||||||
apiRef={apiRef}
|
<Button
|
||||||
handleModal={() => setIsModalOpen(true)}
|
variant="contained"
|
||||||
/>
|
onClick={() => setShowGenerateTokenModal(true)}
|
||||||
),
|
sx={{ backgroundColor: '#1976d2', color: 'white' }}
|
||||||
}}
|
>
|
||||||
pageSize={10}
|
Generate New Token
|
||||||
onGridReady={(gridApi) => {
|
</Button>
|
||||||
apiRef.current = gridApi;
|
}
|
||||||
}}
|
/>
|
||||||
className="data-grid"
|
<CardContent>
|
||||||
/>
|
<Typography variant="body1">
|
||||||
</div>
|
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>
|
</Box>
|
||||||
{/* Add your modals and other components here */}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TokenRegistry;
|
export default TOKENRegistry;
|
||||||
88
src/components/Dashboard/TokenRegistery/tokenregistryapi.js
Normal file
88
src/components/Dashboard/TokenRegistery/tokenregistryapi.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// src/services/tokenRegistryAPI.js
|
||||||
|
import axios from 'axios';
|
||||||
|
import { getToken } from '../../../utils/tokenService';
|
||||||
|
|
||||||
|
// Create axios instance with base configuration
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: process.env.REACT_APP_API_URL || 'http://157.66.191.31:33730/back/',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add request interceptor to inject token
|
||||||
|
apiClient.interceptors.request.use((config) => {
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}, (error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Response interceptor for error handling
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
(response) => response.data,
|
||||||
|
(error) => {
|
||||||
|
if (error.response) {
|
||||||
|
// Handle specific status codes
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
// Handle unauthorized (token expired)
|
||||||
|
console.error('Authentication failed');
|
||||||
|
}
|
||||||
|
throw new Error(error.response.data.message || 'Request failed');
|
||||||
|
}
|
||||||
|
throw new Error(error.message || 'Network error');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchAllTokens = async () => {
|
||||||
|
try {
|
||||||
|
return await apiClient.get('apiregistery/getall');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch tokens error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateToken = async (token_name) => {
|
||||||
|
try {
|
||||||
|
return await apiClient.post(
|
||||||
|
'apiregistery/generateToken',
|
||||||
|
new URLSearchParams({ token_name }),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Generate token error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createToken = async (tokenData) => {
|
||||||
|
try {
|
||||||
|
return await apiClient.post('apiregistery/create', tokenData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Create token error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateToken = async (id, tokenData) => {
|
||||||
|
try {
|
||||||
|
return await apiClient.put(`apiregistery/update/${id}`, tokenData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Update token error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteToken = async (id) => {
|
||||||
|
try {
|
||||||
|
return await apiClient.delete(`apiregistery/delete/${id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete token error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,106 +1,138 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
CircularProgress
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
const UpdateModal = ({ user, onUpdate, onClose }) => {
|
const UpdateModal = ({ user, onUpdate, onClose, open }) => {
|
||||||
const [updatedUser, setUpdatedUser] = useState(() => ({ ...user }));
|
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(() => {
|
useEffect(() => {
|
||||||
console.log('Updated user state:', updatedUser);
|
if (user) {
|
||||||
}, [updatedUser]);
|
setUpdatedUser({ ...user });
|
||||||
|
}
|
||||||
|
}, [user, open]);
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
const updatedData = { ...updatedUser, [field]: value };
|
setUpdatedUser(prev => ({
|
||||||
setUpdatedUser(updatedData);
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
const handleSave = () => {
|
|
||||||
onUpdate(updatedUser);
|
const handleSave = async () => {
|
||||||
|
if (!updatedUser.userId) {
|
||||||
|
setError('User ID is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await onUpdate(updatedUser);
|
||||||
onClose();
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Update failed:', err);
|
||||||
|
setError(err.message || 'Failed to update user');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleUpdate = () => {
|
if (!user) {
|
||||||
// console.log('Before update:', updatedUser);
|
return null; // Or render a loading state/message
|
||||||
// onUpdate(updatedUser);
|
}
|
||||||
|
|
||||||
// onClose();
|
|
||||||
|
|
||||||
|
|
||||||
// // Use a callback function with setUpdatedUser to ensure the state is updated
|
|
||||||
// setUpdatedUser((prev) => {
|
|
||||||
// const updatedData = { ...prev };
|
|
||||||
// console.log('Updated data:', updatedData);
|
|
||||||
|
|
||||||
// onUpdate(updatedData); // Pass the updatedData to onUpdate
|
|
||||||
// onClose();
|
|
||||||
// return updatedData; // Return the updatedData to setUpdatedUser
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Set the updatedUser state directly to ensure the UI reflects the changes
|
|
||||||
// setUpdatedUser(updatedUser);
|
|
||||||
// };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modalWrapper">
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<div className="modal">
|
<DialogTitle>Update User</DialogTitle>
|
||||||
<button className="closeBtn" onClick={onClose}>
|
<DialogContent>
|
||||||
X
|
{error && (
|
||||||
</button>
|
<div style={{ color: 'red', marginBottom: '16px' }}>
|
||||||
<div>User ID: {user.userId}</div>
|
{error}
|
||||||
<div>Username: {user.username}</div>
|
</div>
|
||||||
<div>Full Name: {user.fullName}</div>
|
)}
|
||||||
|
|
||||||
<div>Email: {user.email}</div>
|
<TextField
|
||||||
|
margin="dense"
|
||||||
<div>Mob Number: {user.mobno}</div>
|
label="User ID"
|
||||||
<div>User grp name: {user.usergrpname}</div>
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
<label htmlFor="updatedUserId">User ID:</label>
|
value={updatedUser.userId || ''}
|
||||||
<input
|
|
||||||
id="updatedUserId"
|
|
||||||
type="text"
|
|
||||||
value={updatedUser.userId}
|
|
||||||
onChange={(e) => handleChange('userId', e.target.value)}
|
onChange={(e) => handleChange('userId', e.target.value)}
|
||||||
|
disabled // Typically IDs shouldn't be editable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label htmlFor="updatedUsername">Username:</label>
|
<TextField
|
||||||
<input
|
margin="dense"
|
||||||
id="updatedUsername"
|
label="Username"
|
||||||
type="text"
|
fullWidth
|
||||||
value={updatedUser.username}
|
variant="outlined"
|
||||||
|
value={updatedUser.username || ''}
|
||||||
onChange={(e) => handleChange('username', e.target.value)}
|
onChange={(e) => handleChange('username', e.target.value)}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label htmlFor="updatedEmail">Email:</label>
|
<TextField
|
||||||
<input
|
margin="dense"
|
||||||
id="updatedEmail"
|
label="Full Name"
|
||||||
type="text"
|
fullWidth
|
||||||
value={updatedUser.email}
|
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)}
|
onChange={(e) => handleChange('email', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label htmlFor="updatedFullName">Full Name:</label>
|
<TextField
|
||||||
<input
|
margin="dense"
|
||||||
id="updatedFullName"
|
label="Mobile Number"
|
||||||
type="text"
|
fullWidth
|
||||||
value={updatedUser.fullName}
|
variant="outlined"
|
||||||
onChange={(e) => handleChange('fullName', e.target.value)}
|
value={updatedUser.mobno || ''}
|
||||||
/>
|
|
||||||
<label htmlFor="updatedMobno">Mob No:</label>
|
|
||||||
<input
|
|
||||||
id="updatedmobno"
|
|
||||||
type="number"
|
|
||||||
value={updatedUser.mobno}
|
|
||||||
onChange={(e) => handleChange('mobno', e.target.value)}
|
onChange={(e) => handleChange('mobno', e.target.value)}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="updatedusergrpname">User grp name:</label>
|
|
||||||
<input
|
<TextField
|
||||||
id="updatedusergrpname"
|
margin="dense"
|
||||||
type="text"
|
label="User Group"
|
||||||
value={updatedUser.usergrpname}
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={updatedUser.usergrpname || ''}
|
||||||
onChange={(e) => handleChange('usergrpname', e.target.value)}
|
onChange={(e) => handleChange('usergrpname', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
</DialogContent>
|
||||||
<button onClick={handleSave}>SAVE</button>
|
<DialogActions>
|
||||||
</div>
|
<Button onClick={onClose} color="secondary">
|
||||||
</div>
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,129 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: bold;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Box, Button } from "@mui/material";
|
import { Box, Button, Typography } from "@mui/material";
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||||
import { BsThreeDotsVertical } from "react-icons/bs";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||||
import "./UserGroupMaintance.css";
|
import "./UserGroupMaintance.css";
|
||||||
// eslint-disable-next-line
|
|
||||||
const api = process.env.REACT_APP_API_BASE_URL;
|
const api = process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
function CustomToolbar({ apiRef, handleModal }) {
|
function CustomToolbar({ apiRef, handleModal }) {
|
||||||
@@ -14,9 +15,21 @@ function CustomToolbar({ apiRef, handleModal }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridToolbarContainer>
|
<GridToolbarContainer className="grid-toolbar">
|
||||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
<Button
|
||||||
<Button onClick={handleModal}>+</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>
|
</GridToolbarContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -26,7 +39,7 @@ function UserMaintance() {
|
|||||||
const [selectedUserGroup, setSelectedUserGroup] = useState(null);
|
const [selectedUserGroup, setSelectedUserGroup] = useState(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const apiRef = useRef(null);
|
const apiRef = useRef(null);
|
||||||
// eslint-disable-next-line
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
@@ -136,38 +149,21 @@ function UserMaintance() {
|
|||||||
headerClassName: "custom-header",
|
headerClassName: "custom-header",
|
||||||
cellClassName: "custom-cell",
|
cellClassName: "custom-cell",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: "createdateformated",
|
|
||||||
headerName: "Create Date Formated",
|
|
||||||
width: 100,
|
|
||||||
headerClassName: "custom-header",
|
|
||||||
cellClassName: "custom-cell",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "updatedateformated",
|
|
||||||
headerName: "Update Date Formated",
|
|
||||||
width: 100,
|
|
||||||
headerClassName: "custom-header",
|
|
||||||
cellClassName: "custom-cell",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add other columns as needed
|
|
||||||
{
|
{
|
||||||
field: "actions",
|
field: "actions",
|
||||||
headerName: "Actions",
|
headerName: "Actions",
|
||||||
width: 150,
|
width: 150,
|
||||||
renderCell: ({ row }) => (
|
renderCell: ({ row }) => (
|
||||||
<div>
|
<div style={{ position: 'relative' }}>
|
||||||
<div
|
<div
|
||||||
className="three-dots"
|
className="three-dots"
|
||||||
onClick={() => handleThreeDotsClick(row.usrGrp)}
|
onClick={() => handleThreeDotsClick(row.usrGrp)}
|
||||||
>
|
>
|
||||||
<BsThreeDotsVertical />
|
<FontAwesomeIcon icon={faEllipsisV} />
|
||||||
</div>
|
</div>
|
||||||
{selectedUserGroup === row.usrGrp && (
|
{selectedUserGroup === row.usrGrp && (
|
||||||
<div className="popover">
|
<div className="popover">
|
||||||
<button onClick={() => handleDelete(row.usrGrp)}>Delete</button>
|
<button onClick={() => handleDelete(row.usrGrp)}>Delete</button>
|
||||||
{/* You can include other actions here */}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -176,15 +172,12 @@ function UserMaintance() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4">
|
<div className="user-group-maintenance-container">
|
||||||
<div className="text-center text-3xl text-white bg-gray-400 p-2 rounded-lg">
|
<Typography variant="h4" className="main-title" gutterBottom>
|
||||||
User Group Maintenance
|
User Group Maintenance
|
||||||
</div>
|
</Typography>
|
||||||
|
|
||||||
<Box
|
<Box className="data-grid-container" sx={{ height: 500, width: '100%' }}>
|
||||||
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2 bg-white border border-gray-200 shadow-lg rounded-lg"
|
|
||||||
sx={{ height: 500, width: "100%" }}
|
|
||||||
>
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={userGroups}
|
rows={userGroups}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -192,25 +185,34 @@ function UserMaintance() {
|
|||||||
Toolbar: () => (
|
Toolbar: () => (
|
||||||
<CustomToolbar
|
<CustomToolbar
|
||||||
apiRef={apiRef}
|
apiRef={apiRef}
|
||||||
handleThreeDotsClick={handleThreeDotsClick}
|
|
||||||
handleModal={handleModal}
|
handleModal={handleModal}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
pageSize={10}
|
pageSize={10}
|
||||||
|
rowsPerPageOptions={[10]}
|
||||||
onGridReady={(gridApi) => {
|
onGridReady={(gridApi) => {
|
||||||
apiRef.current = gridApi;
|
apiRef.current = gridApi;
|
||||||
}}
|
}}
|
||||||
className="bg-gray-400"
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Your modals and other components */}
|
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
<div className="modal-overlay">
|
||||||
<div className="bg-white p-8 rounded-lg shadow-lg max-w-lg w-full">
|
<div className="modal-content">
|
||||||
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
|
<Typography variant="h5" className="modal-title">
|
||||||
{/* Modal content here */}
|
Add New User Group
|
||||||
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
|
</Typography>
|
||||||
|
{/* Modal form content here */}
|
||||||
|
<div className="modal-actions">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="toolbar-button"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,168 +1,499 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Box, Button } from "@mui/material";
|
import {
|
||||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
Box,
|
||||||
import { BsThreeDotsVertical } from "react-icons/bs";
|
Button,
|
||||||
import Modal from "./Modal"; // Import your Modal component
|
TextField,
|
||||||
import UpdateModal from "./UpdateModal"; // Import your UpdateModal component
|
Table,
|
||||||
import "./UserMaintance.css";
|
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";
|
||||||
|
|
||||||
const api = process.env.REACT_APP_API_BASE_URL;
|
function UserMaintenanceView() {
|
||||||
|
// State management
|
||||||
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
|
|
||||||
const handleGoToPage1 = () => {
|
|
||||||
if (apiRef.current) {
|
|
||||||
apiRef.current.setPage(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GridToolbarContainer>
|
|
||||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
|
||||||
<Button onClick={handleModal}>+</Button>
|
|
||||||
</GridToolbarContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function UserMaintance() {
|
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [selectedUser, setSelectedUser] = useState(null);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedUserForUpdate, setSelectedUserForUpdate] = useState(null);
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false);
|
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||||
const [newUser, setNewUser] = useState({
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const [columnMenuAnchor, setColumnMenuAnchor] = useState(null);
|
||||||
|
|
||||||
|
// User data state
|
||||||
|
const [userData, setUserData] = useState({
|
||||||
userId: "",
|
userId: "",
|
||||||
username: "",
|
username: "",
|
||||||
fullName: "",
|
fullName: "",
|
||||||
email: "",
|
email: "",
|
||||||
|
mob_no: "",
|
||||||
usrGrpName: "",
|
active: true,
|
||||||
|
usrGrpId: "",
|
||||||
});
|
});
|
||||||
const apiRef = useRef(null);
|
|
||||||
|
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchUsers = async () => {
|
||||||
const token = localStorage.getItem("token");
|
|
||||||
console.log("object", token);
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${api}/api/getAllAppUser`, {
|
setLoading(true);
|
||||||
headers: {
|
const response = await getAllUsers();
|
||||||
Authorization: `Bearer ${token}`,
|
setUsers(response.data || []);
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
const usersWithIds = data.map((user, index) => ({
|
|
||||||
...user,
|
|
||||||
id: index + 1,
|
|
||||||
}));
|
|
||||||
setUsers(usersWithIds);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Error fetching users:", error);
|
||||||
|
toast.error("Failed to fetch users");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchUsers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleThreeDotsClick = (userId) => {
|
// Filter users based on search query
|
||||||
setSelectedUser(userId === selectedUser ? null : userId);
|
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],
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (userId) => {
|
// Modal handlers
|
||||||
console.log("Delete user with ID:", userId);
|
const handleOpenModal = (user = null) => {
|
||||||
};
|
if (user) {
|
||||||
|
setUserData(user);
|
||||||
const handleUpdate = (user) => {
|
setIsEditing(true);
|
||||||
setSelectedUserForUpdate(user);
|
} else {
|
||||||
setIsUpdateModalOpen(true);
|
setUserData({
|
||||||
};
|
userId: "",
|
||||||
|
username: "",
|
||||||
const handleModal = () => {
|
fullName: "",
|
||||||
|
email: "",
|
||||||
|
mob_no: "",
|
||||||
|
active: true,
|
||||||
|
usrGrpId: "",
|
||||||
|
});
|
||||||
|
setIsEditing(false);
|
||||||
|
}
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalSave = (data) => {
|
const handleCloseModal = () => {
|
||||||
setUsers((prevUsers) => [
|
|
||||||
...prevUsers,
|
|
||||||
{ ...data, id: prevUsers.length + 1 },
|
|
||||||
]);
|
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateSave = () => {
|
// Form input handler
|
||||||
setIsUpdateModalOpen(false);
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value, type, checked } = e.target;
|
||||||
|
setUserData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === "checkbox" ? checked : value,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
// Form submission handler
|
||||||
{ field: "userId", headerName: "User ID", width: 200 },
|
const handleSubmit = async (e) => {
|
||||||
{ field: "username", headerName: "Username", width: 200 },
|
e.preventDefault();
|
||||||
{ field: "fullName", headerName: "Full Name", width: 200 },
|
try {
|
||||||
{ field: "email", headerName: "Email", width: 200 },
|
if (isEditing) {
|
||||||
{ field: "usrGrpName", headerName: "User Group", width: 150 },
|
await updateUser(userData);
|
||||||
{
|
toast.success("User updated successfully");
|
||||||
field: "actions",
|
} else {
|
||||||
headerName: "Actions",
|
await createUser(userData);
|
||||||
width: 100,
|
toast.success("User added successfully");
|
||||||
renderCell: ({ row }) => (
|
}
|
||||||
<div>
|
// Refresh user list
|
||||||
<div
|
const response = await getAllUsers();
|
||||||
className="three-dots"
|
setUsers(response.data || []);
|
||||||
onClick={() => handleThreeDotsClick(row.userId)}
|
handleCloseModal();
|
||||||
>
|
} catch (error) {
|
||||||
<BsThreeDotsVertical />
|
console.error("Error saving user:", error);
|
||||||
</div>
|
toast.error("There was an error while submitting the USER.");
|
||||||
{selectedUser === row.userId && (
|
}
|
||||||
<div className="popover">
|
};
|
||||||
<button onClick={() => handleDelete(row.userId)}>Delete</button>
|
|
||||||
<button onClick={() => handleUpdate(row)}>Update</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<Box
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="80vh">
|
||||||
sx={{ height: "calc(100vh - 150px)", width: "100%", overflowX: "auto" }}
|
<CircularProgress />
|
||||||
>
|
|
||||||
<div className="text-center text-3xl text-white bg-gray-400">
|
|
||||||
User Maintenance
|
|
||||||
</div>
|
|
||||||
<DataGrid
|
|
||||||
className="bg-gray-400"
|
|
||||||
rows={users}
|
|
||||||
columns={columns}
|
|
||||||
components={{
|
|
||||||
Toolbar: () => (
|
|
||||||
<CustomToolbar
|
|
||||||
apiRef={apiRef}
|
|
||||||
handleThreeDotsClick={handleThreeDotsClick}
|
|
||||||
handleModal={handleModal}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
pageSize={10}
|
|
||||||
onGridReady={(gridApi) => {
|
|
||||||
apiRef.current = gridApi;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{isModalOpen && (
|
|
||||||
<Modal
|
|
||||||
setNewUser={setNewUser}
|
|
||||||
newUser={newUser}
|
|
||||||
onSave={handleModalSave}
|
|
||||||
onClose={() => setIsModalOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isUpdateModalOpen && (
|
|
||||||
<UpdateModal
|
|
||||||
user={selectedUserForUpdate}
|
|
||||||
onUpdate={handleUpdateSave}
|
|
||||||
onClose={() => setIsUpdateModalOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserMaintance;
|
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;
|
||||||
@@ -121,6 +121,3 @@ button:hover {
|
|||||||
.main-content h3 {
|
.main-content h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,95 @@
|
|||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Sidebar from "./sidebar";
|
import { useNavigate, Link, Routes, Route, Outlet } from "react-router-dom";
|
||||||
import { useNavigate } 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 UserMaintanceComponent from "./UserMaintance";
|
||||||
import UserGroupMaintanceComponent from "./UserGroupMaintance/UserGroupMaintance";
|
import UserGroupMaintanceComponent from "./UserGroupMaintance/UserGroupMaintance";
|
||||||
import MenuMaintanceComponent from "./MenuMaintance/MenuMaintance";
|
import MenuMaintanceComponent from "./MenuMaintance/MenuMaintance";
|
||||||
import MenuAccessControlComponent from "./MenuAccessControl/MenuAccessControl";
|
import MenuAccessControlComponent from "./MenuAccessControl/MenuAccessControl";
|
||||||
import SystemParametersComponent from "./SystemParameters/SystemParameters";
|
import SystemParametersComponent from "./SystemParameters/SystemParameters";
|
||||||
// import AccessTypeComponent from "./AccessType/AccessType";
|
import AccessTypeComponent from "./AccessType/AccessType";
|
||||||
import ApiRegistery from "./ApiRegistery/ApiRegistery";
|
import ApiRegistery from "./ApiRegistery/ApiRegistery";
|
||||||
import TokenRegistery from "./TokenRegistery/TokenRegistery";
|
import TokenRegistery from "./TokenRegistery/TokenRegistery";
|
||||||
import HomePage from "./HomePage";
|
import HomePage from "./HomePage";
|
||||||
import Setup from "./Setup.js";
|
import Setup from "./Setup";
|
||||||
import Report from "./Report";
|
import Report from "./reports/Report";
|
||||||
import { FaCog, FaUsers, FaSignOutAlt, FaHome, FaChartBar } from "react-icons/fa";
|
import SequenceGenerator from "./document sequence/sequencegenerator";
|
||||||
|
import About from "./dropdown/about";
|
||||||
|
import Profile from "./dropdown/profile";
|
||||||
|
import DashboardRunnerAll from "./../dashboardnew/dashboardrunner/dashboardrunnerall";
|
||||||
|
import DashboardNewAll from "../dashboardnew/dashboardbuildernewall";
|
||||||
|
import DashboardNewAdd from "../dashboardnew/dashboardadd/dashboardbuilderadd";
|
||||||
|
import DashboardNewEdit from "../dashboardnew/editdashboard/editformdashboard";
|
||||||
|
import EditNewDash from "../dashboardnew/editdashboard/editdashboard";
|
||||||
|
import DashboardRunner from "../dashboardnew/dashboardrunner/dashboardrunner";
|
||||||
|
import SubMenuMaintenance from "./sub menu/submenumaintanence";
|
||||||
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
const [menus, setMenus] = useState([]);
|
const [menus, setMenus] = useState([]);
|
||||||
const [selectedUserMaintance, setSelectedUserMaintance] = useState(null);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
const [openTransaction, setOpenTransaction] = useState(false);
|
||||||
const [content, setContent] = useState("");
|
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(() => {
|
useEffect(() => {
|
||||||
|
const user = JSON.parse(localStorage.getItem("user"));
|
||||||
|
setCurrentUser(user);
|
||||||
|
|
||||||
const fetchMenusData = async () => {
|
const fetchMenusData = async () => {
|
||||||
const token = localStorage.getItem("authtoken");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
const apiUrl = `${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`;
|
const apiUrl = `${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`;
|
||||||
console.log("Fetching menus from API:", apiUrl);
|
console.log("Fetching menus from API:", apiUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: "GET",
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -58,142 +114,490 @@ const Dashboard = () => {
|
|||||||
fetchMenusData();
|
fetchMenusData();
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const handleMenuItemClick = (menuItem) => {
|
|
||||||
setSelectedUserMaintance(menuItem);
|
|
||||||
setContent(menuItem.menuItemDesc); // Update content based on clicked menu item
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHomeClick = () => {
|
const handleHomeClick = () => {
|
||||||
setSelectedUserMaintance(null);
|
navigate('/dashboard');
|
||||||
setContent("Home");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetupClick = () => {
|
const handleSetupClick = () => {
|
||||||
setSelectedUserMaintance(null);
|
navigate('/dashboard/setup');
|
||||||
setContent("Setup");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReportClick = () => {
|
const handleReportClick = () => {
|
||||||
setSelectedUserMaintance(null);
|
navigate('/dashboard/reports');
|
||||||
setContent("Report");
|
};
|
||||||
|
|
||||||
|
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 = () => {
|
const handleSidebarToggle = () => {
|
||||||
setSidebarCollapsed(!sidebarCollapsed);
|
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 = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem("authToken");
|
localStorage.removeItem("authToken");
|
||||||
localStorage.removeItem("user");
|
localStorage.removeItem("user");
|
||||||
navigate("/");
|
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 (
|
return (
|
||||||
<div className="flex flex-col h-screen bg-gradient-to-br from-purple-50 via-white to-blue-50">
|
<div className="flex flex-col h-screen">
|
||||||
{/* Top Navigation Bar */}
|
{/* Navbar - fixed at the top */}
|
||||||
<div className="flex justify-between items-center bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-4 shadow-lg">
|
<div className="h-16 flex-shrink-0 bg-gray-800 text-white p-4 z-10">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex justify-between items-center h-full">
|
||||||
<h3 className="text-2xl font-bold">Dashboard</h3>
|
<div className="flex items-center">
|
||||||
<div className="h-6 w-px bg-white/30"></div>
|
<IconButton
|
||||||
<nav className="flex space-x-6">
|
color="inherit"
|
||||||
<button
|
onClick={toggleMobileSidebar}
|
||||||
onClick={handleHomeClick}
|
sx={{ display: { xs: 'block', lg: 'none' }, mr: 2 }}
|
||||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
|
||||||
>
|
>
|
||||||
<FaHome className="w-5 h-5" />
|
<MenuIcon />
|
||||||
<span>Home</span>
|
</IconButton>
|
||||||
</button>
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||||
<button
|
Dashboard
|
||||||
onClick={handleSetupClick}
|
</Typography>
|
||||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
|
||||||
>
|
|
||||||
<FaCog className="w-5 h-5" />
|
|
||||||
<span>Setup</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleReportClick}
|
|
||||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
|
||||||
>
|
|
||||||
<FaChartBar className="w-5 h-5" />
|
|
||||||
<span>Reports</span>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="flex items-center space-x-2 bg-white/10 hover:bg-white/20 text-white font-semibold py-2 px-4 rounded-lg transition-all duration-200"
|
|
||||||
onClick={handleLogout}
|
|
||||||
>
|
|
||||||
<FaSignOutAlt className="w-5 h-5" />
|
|
||||||
<span>Logout</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 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">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
{/* Sidebar */}
|
{/* Integrated Sidebar */}
|
||||||
<div
|
<Drawer
|
||||||
className={`bg-gradient-to-b from-purple-700 to-indigo-800 text-white transition-all duration-300 ${
|
variant="permanent"
|
||||||
sidebarCollapsed ? "w-16" : "w-64"
|
sx={{
|
||||||
} min-w-16 shadow-xl`}
|
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',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex justify-end p-2">
|
<DrawerHeader>
|
||||||
<button
|
<IconButton onClick={handleSidebarToggle} size="small">
|
||||||
onClick={handleSidebarToggle}
|
{sidebarCollapsed ? <ChevronRight /> : <ChevronLeft />}
|
||||||
className="text-white/80 hover:text-white bg-white/10 hover:bg-white/20 p-2 rounded-lg transition-all duration-200"
|
</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)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{sidebarCollapsed ? ">>" : "<<"}
|
<ListItemIcon
|
||||||
</button>
|
sx={{
|
||||||
</div>
|
minWidth: 0,
|
||||||
<Sidebar
|
mr: sidebarCollapsed ? 'auto' : 3,
|
||||||
menus={menus}
|
justifyContent: 'center',
|
||||||
handleMenuItemClick={handleMenuItemClick}
|
color: 'inherit'
|
||||||
collapsed={sidebarCollapsed}
|
}}
|
||||||
setCollapsed={setSidebarCollapsed}
|
>
|
||||||
/>
|
<SwapHorizIcon />
|
||||||
</div>
|
</ListItemIcon>
|
||||||
|
{!sidebarCollapsed && (
|
||||||
{/* Main Content Area */}
|
<>
|
||||||
<div className="flex-1 p-6 overflow-auto bg-gradient-to-br from-purple-50 via-white to-blue-50">
|
<ListItemText primary="Transaction" />
|
||||||
<div className="max-w-7xl mx-auto">
|
{openTransaction ? <ExpandLess /> : <ExpandMore />}
|
||||||
{content === "Setup" ? (
|
</>
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<Setup />
|
|
||||||
</div>
|
|
||||||
) : content === "Home" ? (
|
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<HomePage />
|
|
||||||
</div>
|
|
||||||
) : content === "Report" ? (
|
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<Report />
|
|
||||||
</div>
|
|
||||||
) : selectedUserMaintance ? (
|
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<h3 className="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b border-purple-100">
|
|
||||||
{selectedUserMaintance.menuItemDesc}
|
|
||||||
</h3>
|
|
||||||
{selectedUserMaintance.menuItemDesc === "User Maintance" ? (
|
|
||||||
<UserMaintanceComponent />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "User Group Maintance" ? (
|
|
||||||
<UserGroupMaintanceComponent />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "Menu Maintance" ? (
|
|
||||||
<MenuMaintanceComponent />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "Menu Access Control" ? (
|
|
||||||
<MenuAccessControlComponent />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "System Parameters" ? (
|
|
||||||
<SystemParametersComponent />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "Api Registery" ? (
|
|
||||||
<ApiRegistery />
|
|
||||||
) : selectedUserMaintance.menuItemDesc === "Token Registery" ? (
|
|
||||||
<TokenRegistery />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
||||||
<HomePage />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</ListItemButton>
|
||||||
</div>
|
</ListItem>
|
||||||
|
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
|
||||||
|
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||||
|
<ListItemButton
|
||||||
|
sx={{ pl: 4 }}
|
||||||
|
onClick={() => navigate('/admin/regform')}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||||
|
<DescriptionIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Regform" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
sx={{ pl: 4 }}
|
||||||
|
onClick={() => navigate('/admin/error404')}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||||
|
<ErrorIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Additional container" />
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Masters (single item) */}
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => navigate('/admin/masters')}
|
||||||
|
sx={{
|
||||||
|
minHeight: 48,
|
||||||
|
justifyContent: sidebarCollapsed ? 'center' : 'initial',
|
||||||
|
px: 2.5,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 0,
|
||||||
|
mr: sidebarCollapsed ? 'auto' : 3,
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StorageIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<ListItemText primary="Masters" />
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{/* Dynamic menu items from API */}
|
||||||
|
{menus.map((menu) => (
|
||||||
|
<ListItem key={menu.menuItemId} disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => handleMenuItemClick(menu)}
|
||||||
|
sx={{
|
||||||
|
minHeight: 48,
|
||||||
|
justifyContent: sidebarCollapsed ? 'center' : 'initial',
|
||||||
|
px: 2.5,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 0,
|
||||||
|
mr: sidebarCollapsed ? 'auto' : 3,
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StorageIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<ListItemText primary={menu.menuItemDesc} />
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
{/* Content area */}
|
||||||
|
<Box
|
||||||
|
component="main"
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
p: 3,
|
||||||
|
width: {
|
||||||
|
xs: '100%',
|
||||||
|
lg: `calc(100% - ${sidebarCollapsed ? collapsedWidth : drawerWidth}px)`
|
||||||
|
},
|
||||||
|
marginLeft: {
|
||||||
|
xs: 0,
|
||||||
|
lg: `${sidebarCollapsed ? collapsedWidth : drawerWidth}px`
|
||||||
|
},
|
||||||
|
transition: theme.transitions.create(['width', 'margin'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
height: 'calc(100vh - 64px)',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
<Route index element={<HomePage />} />
|
||||||
|
<Route path="setup" element={<Setup />}>
|
||||||
|
<Route index element={<div>Select a setup option from the menu</div>} />
|
||||||
|
<Route path="user-maintenance" element={<UserMaintanceComponent />} />
|
||||||
|
<Route path="user-group-maintenance" element={<UserGroupMaintanceComponent />} />
|
||||||
|
<Route path="menu-maintenance" element={<MenuMaintanceComponent />} />
|
||||||
|
<Route path="menu-access-control" element={<MenuAccessControlComponent />} />
|
||||||
|
<Route path="system-parameters" element={<SystemParametersComponent />} />
|
||||||
|
<Route path="access-type" element={<AccessTypeComponent />} />
|
||||||
|
<Route path="api-registry" element={<ApiRegistery />} />
|
||||||
|
<Route path="token-registry" element={<TokenRegistery />} />
|
||||||
|
<Route path="document-sequence" element={<SequenceGenerator />} />
|
||||||
|
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
|
||||||
|
</Route>
|
||||||
|
<Route path="reports" element={<Report />} />
|
||||||
|
<Route path="about" element={<About/>} />
|
||||||
|
<Route path="profile" element={<Profile/>} />
|
||||||
|
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
|
||||||
|
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
|
||||||
|
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
|
||||||
|
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
|
||||||
|
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
|
||||||
|
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
|
||||||
|
|
||||||
|
<Route path="*" element={<div>Page Not Found</div>} />
|
||||||
|
</Routes>
|
||||||
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
409
src/components/Dashboard/document sequence/sequencegenerator.js
Normal file
409
src/components/Dashboard/document sequence/sequencegenerator.js
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
Alert,
|
||||||
|
Snackbar,
|
||||||
|
InputAdornment,
|
||||||
|
TablePagination,
|
||||||
|
Divider,
|
||||||
|
Chip
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
MoreVert as MoreVertIcon,
|
||||||
|
Add as AddIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Close as CloseIcon
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import SequenceGeneratorAPI from './sequencegeneratorapi';
|
||||||
|
|
||||||
|
const SequenceGenerator = () => {
|
||||||
|
const [sequences, setSequences] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
const [editingId, setEditingId] = useState(null);
|
||||||
|
const [currentSequence, setCurrentSequence] = useState(null);
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const [openMenuId, setOpenMenuId] = useState(null);
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||||
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||||
|
const [snackbarSeverity, setSnackbarSeverity] = useState('success');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSequences();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchSequences = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await SequenceGeneratorAPI.getAll();
|
||||||
|
setSequences(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.response?.data?.message || 'Failed to fetch sequences');
|
||||||
|
showSnackbar(err.response?.data?.message || 'Failed to fetch sequences', 'error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSnackbar = (message, severity) => {
|
||||||
|
setSnackbarMessage(message);
|
||||||
|
setSnackbarSeverity(severity);
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
setCurrentSequence(null);
|
||||||
|
setEditingId(null);
|
||||||
|
setOpenDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record) => {
|
||||||
|
setCurrentSequence(record);
|
||||||
|
setEditingId(record.id);
|
||||||
|
setOpenDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
try {
|
||||||
|
await SequenceGeneratorAPI.delete(id);
|
||||||
|
showSnackbar('Sequence deleted successfully', 'success');
|
||||||
|
fetchSequences();
|
||||||
|
} catch (err) {
|
||||||
|
showSnackbar(err.response?.data?.message || 'Failed to delete sequence', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuClick = (event, id) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
setOpenMenuId(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
setOpenMenuId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get form values directly from state or form inputs
|
||||||
|
const form = event?.currentTarget || document.querySelector('form');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const values = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
|
// Convert number fields from strings to numbers
|
||||||
|
values.starting_no = Number(values.starting_no);
|
||||||
|
values.current_no = Number(values.current_no);
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
await SequenceGeneratorAPI.update(editingId, values);
|
||||||
|
showSnackbar('Sequence updated successfully', 'success');
|
||||||
|
} else {
|
||||||
|
await SequenceGeneratorAPI.create(values);
|
||||||
|
showSnackbar('Sequence created successfully', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenDialog(false);
|
||||||
|
fetchSequences();
|
||||||
|
} catch (err) {
|
||||||
|
showSnackbar(err.response?.data?.message || 'Operation failed', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleChangePage = (event, newPage) => {
|
||||||
|
setPage(newPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeRowsPerPage = (event) => {
|
||||||
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
|
setPage(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ id: 'sequence_name', label: 'Name' },
|
||||||
|
{ id: 'sequence_code', label: 'Sequence Code' },
|
||||||
|
{ id: 'current_no', label: 'Current No' },
|
||||||
|
{ id: 'demonstration', label: 'Demonstration' },
|
||||||
|
{ id: 'actions', label: 'Actions' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
{/* Error Alert */}
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
onClose={() => setError(null)}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Success/Error Snackbar */}
|
||||||
|
<Snackbar
|
||||||
|
open={snackbarOpen}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={() => setSnackbarOpen(false)}
|
||||||
|
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={() => setSnackbarOpen(false)}
|
||||||
|
severity={snackbarSeverity}
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{snackbarMessage}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
|
||||||
|
{/* Header and Add Button */}
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
|
<Typography variant="h4" component="h2">
|
||||||
|
Document Sequence
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={handleCreate}
|
||||||
|
>
|
||||||
|
Add Sequence
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
|
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
|
||||||
|
<Table>
|
||||||
|
<TableHead sx={{ backgroundColor: 'primary.main' }}>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell key={column.id} sx={{ color: 'common.white', fontWeight: 'bold' }}>
|
||||||
|
{column.label}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{sequences
|
||||||
|
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||||
|
.map((row) => (
|
||||||
|
<TableRow key={row.id} hover>
|
||||||
|
<TableCell>{row.sequence_name}</TableCell>
|
||||||
|
<TableCell>{row.sequence_code}</TableCell>
|
||||||
|
<TableCell>{row.current_no}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip
|
||||||
|
label={row.demonstration}
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton
|
||||||
|
aria-label="more"
|
||||||
|
aria-controls={`menu-${row.id}`}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(e) => handleMenuClick(e, row.id)}
|
||||||
|
>
|
||||||
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id={`menu-${row.id}`}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={openMenuId === row.id}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => { handleEdit(row); handleMenuClose(); }}>
|
||||||
|
<EditIcon sx={{ mr: 1 }} /> Edit
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleDelete(row.id);
|
||||||
|
handleMenuClose();
|
||||||
|
}}
|
||||||
|
sx={{ color: 'error.main' }}
|
||||||
|
>
|
||||||
|
<DeleteIcon sx={{ mr: 1 }} /> Delete
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25]}
|
||||||
|
component="div"
|
||||||
|
count={sequences.length}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
page={page}
|
||||||
|
onPageChange={handleChangePage}
|
||||||
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Add/Edit Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={openDialog}
|
||||||
|
onClose={() => setOpenDialog(false)}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
{editingId ? 'Update Sequence Generator' : 'Add New Sequence'}
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={() => setOpenDialog(false)}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
color: (theme) => theme.palette.grey[500],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
{currentSequence && (
|
||||||
|
<Card variant="outlined" sx={{ mb: 3 }}>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Sequence Details
|
||||||
|
</Typography>
|
||||||
|
<Divider sx={{ mb: 2 }} />
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex' }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ width: '30%', fontWeight: 'bold' }}>
|
||||||
|
Sequence ID
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">{currentSequence.id}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex' }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ width: '30%', fontWeight: 'bold' }}>
|
||||||
|
Demonstration
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label={currentSequence.demonstration}
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box component="form" id="sequence-form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Name*"
|
||||||
|
name="sequence_name"
|
||||||
|
defaultValue={currentSequence?.sequence_name || ''}
|
||||||
|
required
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Sequence Code"
|
||||||
|
name="sequence_code"
|
||||||
|
defaultValue={currentSequence?.sequence_code || ''}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Prefix*"
|
||||||
|
name="prefix"
|
||||||
|
defaultValue={currentSequence?.prefix || ''}
|
||||||
|
required
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Suffix"
|
||||||
|
name="suffix"
|
||||||
|
defaultValue={currentSequence?.suffix || ''}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Separator"
|
||||||
|
name="seperator"
|
||||||
|
defaultValue={currentSequence?.seperator || ''}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Starting No"
|
||||||
|
name="starting_no"
|
||||||
|
type="number"
|
||||||
|
defaultValue={currentSequence?.starting_no || 1}
|
||||||
|
InputProps={{ inputProps: { min: 1 } }}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Current No"
|
||||||
|
name="current_no"
|
||||||
|
type="number"
|
||||||
|
defaultValue={currentSequence?.current_no || 1}
|
||||||
|
InputProps={{ inputProps: { min: 1 } }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenDialog(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
form="sequence-form" // Associate with the form
|
||||||
|
>
|
||||||
|
{editingId ? 'Update' : 'Create'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SequenceGenerator;
|
||||||
@@ -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;
|
||||||
53
src/components/Dashboard/dropdown/about.js
Normal file
53
src/components/Dashboard/dropdown/about.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Example About component with MUI styling
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
CardMedia,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
const About = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
p: 4,
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
image="/about-image.png"
|
||||||
|
alt="About Us"
|
||||||
|
sx={{
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
objectFit: "cover",
|
||||||
|
mb: 4,
|
||||||
|
borderRadius: 1
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography variant="h4" component="h1" sx={{ mb: 2, color: theme.palette.primary.main }}>
|
||||||
|
About Our Application
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" sx={{ maxWidth: 600, mb: 2 }}>
|
||||||
|
This is a comprehensive dashboard application built with React and Material-UI.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" sx={{ maxWidth: 600 }}>
|
||||||
|
Create new projects for users with appropriate access levels. Contact your administrator
|
||||||
|
if you need additional permissions.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default About;
|
||||||
207
src/components/Dashboard/dropdown/profile.js
Normal file
207
src/components/Dashboard/dropdown/profile.js
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Typography,
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Divider,
|
||||||
|
Box,
|
||||||
|
Stack,
|
||||||
|
IconButton
|
||||||
|
} from '@mui/material';
|
||||||
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [preview, setPreview] = useState(null);
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem("authToken");
|
||||||
|
navigate('/auth/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
setSelectedFile(file);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
setPreview(reader.result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerFileInput = () => {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setPreview(null);
|
||||||
|
setSelectedFile(null);
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
if (!selectedFile) return;
|
||||||
|
alert('Upload functionality would go here');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md" sx={{ mt: 4 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
My Profile Settings
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Profile Photo Section */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
PROFILE PHOTO
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" alignItems="center" spacing={3} sx={{ mb: 2 }}>
|
||||||
|
<Box>
|
||||||
|
{preview ? (
|
||||||
|
<Avatar
|
||||||
|
src={preview}
|
||||||
|
alt="Profile Preview"
|
||||||
|
sx={{ width: 100, height: 100 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Avatar sx={{ width: 100, height: 100, bgcolor: 'grey.300' }} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
accept="image/*"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<CloudUploadIcon />}
|
||||||
|
onClick={triggerFileInput}
|
||||||
|
>
|
||||||
|
Choose File
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Action buttons - shown only when an image is selected */}
|
||||||
|
{preview && (
|
||||||
|
<Stack direction="row" spacing={2} justifyContent="flex-end">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
onClick={handleCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
onClick={handleUpload}
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Profile Form */}
|
||||||
|
<Box component="form" sx={{ mb: 4 }}>
|
||||||
|
<TextField
|
||||||
|
label="Your Full Name"
|
||||||
|
value="sysadmin"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Pronouns"
|
||||||
|
placeholder="Enter Pronouns"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Role"
|
||||||
|
placeholder="Enter Role"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Department or Team"
|
||||||
|
placeholder="Enter Department"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Email"
|
||||||
|
value="sysadmin"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="About Me"
|
||||||
|
placeholder="Enter Description"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
onClick={() => navigate('/error404')}
|
||||||
|
>
|
||||||
|
Update Profile
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
|
<Typography paragraph>
|
||||||
|
<strong>Password:</strong> Change Password for your account{' '}
|
||||||
|
<Link to="/admin/resetpassword">Change password</Link>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
|
<Typography paragraph>
|
||||||
|
<strong>Security:</strong> Logout of all sessions except this current browser{' '}
|
||||||
|
<Button color="primary" onClick={handleLogout}>
|
||||||
|
Logout other sessions
|
||||||
|
</Button>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
|
<Typography paragraph>
|
||||||
|
<strong>Deactivation:</strong> Remove access to all organizations and workspace in CloudnSure{' '}
|
||||||
|
<Button color="primary" onClick={handleLogout}>
|
||||||
|
Deactivate account
|
||||||
|
</Button>
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
||||||
@@ -37,12 +37,6 @@ const Report = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
console.error("Unauthorized. Redirecting to login.");
|
|
||||||
// Redirect to the login page here if needed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch data");
|
throw new Error("Failed to fetch data");
|
||||||
}
|
}
|
||||||
701
src/components/Dashboard/reports/reportbuildersql.js
Normal file
701
src/components/Dashboard/reports/reportbuildersql.js
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
CircularProgress,
|
||||||
|
Pagination,
|
||||||
|
Chip,
|
||||||
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
|
InputAdornment,
|
||||||
|
Tooltip,
|
||||||
|
Divider
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Add as AddIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Settings as SettingsIcon,
|
||||||
|
ArrowBack as ArrowBackIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
PlayArrow as PlayArrowIcon,
|
||||||
|
ViewColumn as ViewColumnIcon,
|
||||||
|
List as ListIcon,
|
||||||
|
MoreVert as MoreVertIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import ReportSqlBuilderApi from "APIServices/ReportSQLBuilderAPI";
|
||||||
|
import { addSQLReport } from "APIServices/AddReportSQLBuilderAPI";
|
||||||
|
import { UpdateReportSQLBuilder } from "APIServices/UpdateReportSQLBuilderAPI";
|
||||||
|
import { deleteReportSQL } from "APIServices/DeleteReportSQLAPI";
|
||||||
|
import ConfirmModal from "../../common/ConfirmModal";
|
||||||
|
import ReportBuilderService from "../../../APIServices/ReportBuilderService";
|
||||||
|
|
||||||
|
function UserDetailsView() {
|
||||||
|
const [userDetails, setUserDetails] = useState([]);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [selectedReport, setSelectedReport] = useState(null);
|
||||||
|
const [editData, setEditData] = useState({
|
||||||
|
reportName: "",
|
||||||
|
description: "",
|
||||||
|
active: false,
|
||||||
|
});
|
||||||
|
const [newData, setNewData] = useState({
|
||||||
|
reportName: "",
|
||||||
|
description: "",
|
||||||
|
active: false,
|
||||||
|
isSql: true,
|
||||||
|
Rpt_builder2_lines: [{ model: "" }],
|
||||||
|
});
|
||||||
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||||
|
const [deleteItem, setDeleteItem] = useState({ index: null, id: null });
|
||||||
|
const [showReportConfigModal, setShowReportConfigModal] = useState(false);
|
||||||
|
const [reportConfigData, setReportConfigData] = useState({
|
||||||
|
conn_name: "",
|
||||||
|
date_param_req: "",
|
||||||
|
std_param_html: "",
|
||||||
|
adhoc_param_html: "",
|
||||||
|
column_str: "",
|
||||||
|
sql_str: "",
|
||||||
|
id: null
|
||||||
|
});
|
||||||
|
const [databaseConnections, setDatabaseConnections] = useState([]);
|
||||||
|
|
||||||
|
const fetchUserDetails = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const data = await ReportSqlBuilderApi.fetchUserDetails();
|
||||||
|
setUserDetails(data);
|
||||||
|
toast.success('Data fetched successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user details:', error);
|
||||||
|
toast.error('Failed to fetch user details.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserDetails();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePlayClick = () => {
|
||||||
|
if (selectedReport) {
|
||||||
|
goToRunner(selectedReport);
|
||||||
|
} else {
|
||||||
|
toast.error("No report selected to run.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecordsPerPageChange = (event) => {
|
||||||
|
setRecordsPerPage(parseInt(event.target.value, 10));
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
const { name, value, checked, type } = event.target;
|
||||||
|
const inputValue = type === "checkbox" ? checked : value;
|
||||||
|
|
||||||
|
if (showModal) {
|
||||||
|
setEditData((prev) => ({ ...prev, [name]: inputValue }));
|
||||||
|
} else if (showAddModal) {
|
||||||
|
setNewData((prev) => ({ ...prev, [name]: inputValue }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const updatedReport = await UpdateReportSQLBuilder(editData.id, editData);
|
||||||
|
toast.success("Report Updated successfully..");
|
||||||
|
const updatedDetails = userDetails.map((detail) =>
|
||||||
|
detail.id === editData.id ? { ...detail, ...editData } : detail
|
||||||
|
);
|
||||||
|
setUserDetails(updatedDetails);
|
||||||
|
setShowModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating the report: ", error);
|
||||||
|
toast.error("Failed to update report");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredUsers = userDetails.filter(
|
||||||
|
(item) =>
|
||||||
|
item.reportName &&
|
||||||
|
item.reportName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const slicedUsers = filteredUsers.slice(
|
||||||
|
(currentPage - 1) * recordsPerPage,
|
||||||
|
currentPage * recordsPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(filteredUsers.length / recordsPerPage);
|
||||||
|
|
||||||
|
const handlePageChange = (event, page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGoTo = (data) => {
|
||||||
|
const hasReportData = data.rpt_builder2_lines?.some(line => line.model && line.model.trim() !== "");
|
||||||
|
if (hasReportData) {
|
||||||
|
fetchReportConfiguration(data.id, data);
|
||||||
|
} else {
|
||||||
|
navigate("/admin/reportquery", { state: { id: data.id, reportData: data } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (report) => {
|
||||||
|
fetchReportConfiguration(report.id, report);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDatabaseConnections = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await ReportBuilderService.getDatabase();
|
||||||
|
if (response && response.data) {
|
||||||
|
setDatabaseConnections(response.data);
|
||||||
|
} else {
|
||||||
|
setDatabaseConnections([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching database connections: ", error);
|
||||||
|
toast.error("Failed to load database connections");
|
||||||
|
setDatabaseConnections([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchReportConfiguration = async (reportId, report) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await ReportBuilderService.getrbLineDetailsById(report.rpt_builder2_lines[0].id);
|
||||||
|
await fetchDatabaseConnections();
|
||||||
|
|
||||||
|
if (response && response.data) {
|
||||||
|
let configData = {};
|
||||||
|
try {
|
||||||
|
if (report.Rpt_builder2_lines && report.Rpt_builder2_lines.length > 0) {
|
||||||
|
const modelData = JSON.parse(report.Rpt_builder2_lines[0].model);
|
||||||
|
configData = {
|
||||||
|
conn_name: modelData.conn_name || "",
|
||||||
|
date_param_req: modelData.date_param_req || "",
|
||||||
|
std_param_html: modelData.std_param_html || "",
|
||||||
|
adhoc_param_html: modelData.adhoc_param_html || "",
|
||||||
|
column_str: modelData.column_str || "",
|
||||||
|
sql_str: modelData.sql_str || "",
|
||||||
|
id: report.rpt_builder2_lines[0].id
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
configData = {
|
||||||
|
conn_name: "",
|
||||||
|
date_param_req: "",
|
||||||
|
std_param_html: "",
|
||||||
|
adhoc_param_html: "",
|
||||||
|
column_str: "",
|
||||||
|
sql_str: "",
|
||||||
|
id: report.rpt_builder2_lines[0].id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing report configuration: ", error);
|
||||||
|
toast.error("Failed to parse report configuration");
|
||||||
|
configData = {
|
||||||
|
conn_name: "",
|
||||||
|
date_param_req: "",
|
||||||
|
std_param_html: "",
|
||||||
|
adhoc_param_html: "",
|
||||||
|
column_str: "",
|
||||||
|
sql_str: "",
|
||||||
|
id: report.rpt_builder2_lines[0].id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
setReportConfigData(configData);
|
||||||
|
setShowReportConfigModal(true);
|
||||||
|
} else {
|
||||||
|
toast.error("No configuration data found for this report");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching report configuration: ", error);
|
||||||
|
toast.error("Failed to load report configuration");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReportConfigClose = () => {
|
||||||
|
setShowReportConfigModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReportConfigChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setReportConfigData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReportConfigUpdate = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const reportData = {
|
||||||
|
conn_name: reportConfigData.conn_name,
|
||||||
|
sql_str: reportConfigData.sql_str,
|
||||||
|
date_param_req: reportConfigData.date_param_req,
|
||||||
|
column_str: reportConfigData.column_str,
|
||||||
|
adhoc_param_html: reportConfigData.adhoc_param_html,
|
||||||
|
std_param_html: reportConfigData.std_param_html,
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveReportData = {
|
||||||
|
header_id: reportConfigData.id,
|
||||||
|
model: JSON.stringify(reportData),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await ReportBuilderService.updaterbLineData(saveReportData, reportConfigData.id);
|
||||||
|
toast.success("Report configuration updated successfully");
|
||||||
|
setShowReportConfigModal(false);
|
||||||
|
fetchUserDetails();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating report configuration: ", error);
|
||||||
|
toast.error("Failed to update report configuration");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const { id } = deleteItem;
|
||||||
|
if (!id) {
|
||||||
|
toast.error("Invalid ID. Deletion failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletedReport = await deleteReportSQL(id);
|
||||||
|
toast.success("Report deleted successfully");
|
||||||
|
setUserDetails(userDetails.filter(item => item.id !== id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in deleting report:", error);
|
||||||
|
toast.error("Failed to delete report");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (e, index, id, reportName) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setDeleteItem({ index, id, reportName });
|
||||||
|
setShowConfirmModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (detail) => {
|
||||||
|
setEditData(detail);
|
||||||
|
setShowModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddClose = () => {
|
||||||
|
setShowAddModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const addedReport = await addSQLReport(newData);
|
||||||
|
setUserDetails((prevUserDetails) => [...prevUserDetails, addedReport]);
|
||||||
|
setNewData({
|
||||||
|
reportName: "",
|
||||||
|
description: "",
|
||||||
|
active: false,
|
||||||
|
isSql: true
|
||||||
|
});
|
||||||
|
setShowAddModal(false);
|
||||||
|
toast.success("User report added successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to add the user report. Please try again.");
|
||||||
|
console.error("Error adding user report: ", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToRunner = (report) => {
|
||||||
|
if (!report || typeof report.isSql === "undefined") {
|
||||||
|
console.error("Invalid report object:", report);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.isSql) {
|
||||||
|
navigate(`/admin/report-runner/${report.id}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/admin/report-runner2/${report.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 4 }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 4
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4" component="h2">
|
||||||
|
Report Builder (SQL)
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<PlayArrowIcon />}
|
||||||
|
onClick={() => navigate('/admin/report-runner')}
|
||||||
|
>
|
||||||
|
Report Runner
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={() => setShowAddModal(true)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
|
||||||
|
<TextField
|
||||||
|
placeholder="Search"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
endAdornment: searchQuery && (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => setSearchQuery("")}>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
sx={{ width: '50%' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TableContainer component={Paper} sx={{ mb: 3 }}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow sx={{ backgroundColor: 'primary.main' }}>
|
||||||
|
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Go To</TableCell>
|
||||||
|
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Report Name</TableCell>
|
||||||
|
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Description</TableCell>
|
||||||
|
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Status</TableCell>
|
||||||
|
<TableCell sx={{ color: 'white', fontWeight: 'bold' }}>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{slicedUsers.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} align="center">
|
||||||
|
No records found.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
slicedUsers.filter((detail) => detail.isSql).map((detail, index) => (
|
||||||
|
<TableRow
|
||||||
|
key={index}
|
||||||
|
hover
|
||||||
|
onClick={() => handleRowClick(detail)}
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleGoTo(detail);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon color="primary" />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{detail.reportName}</TableCell>
|
||||||
|
<TableCell>{detail.description || "No description available"}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip
|
||||||
|
label={detail.active ? "Active" : "Inactive"}
|
||||||
|
color={detail.active ? "success" : "error"}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleEdit(detail);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon color="primary" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
confirmDelete(e, index, detail.id, detail.reportName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon color="error" />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Rows per page:
|
||||||
|
</Typography>
|
||||||
|
<Select
|
||||||
|
value={recordsPerPage}
|
||||||
|
onChange={handleRecordsPerPageChange}
|
||||||
|
size="small"
|
||||||
|
sx={{ height: '36px' }}
|
||||||
|
>
|
||||||
|
{[5, 10, 25, 50].map((option) => (
|
||||||
|
<MenuItem key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Pagination
|
||||||
|
count={totalPages}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
color="primary"
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Edit Dialog */}
|
||||||
|
<Dialog open={showModal} onClose={handleClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>Edit User Detail</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
|
||||||
|
<TextField
|
||||||
|
label="Report Name"
|
||||||
|
name="reportName"
|
||||||
|
value={editData.reportName || ""}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={editData.description || ""}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
name="active"
|
||||||
|
checked={editData.active || false}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Active"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose}>Cancel</Button>
|
||||||
|
<Button onClick={handleSubmit} variant="contained">Save Changes</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Add Dialog */}
|
||||||
|
<Dialog open={showAddModal} onClose={handleAddClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>Add New User Detail</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
|
||||||
|
<TextField
|
||||||
|
label="Report Name"
|
||||||
|
name="reportName"
|
||||||
|
value={newData.reportName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={newData.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
name="active"
|
||||||
|
checked={newData.active}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Active"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleAddClose}>Cancel</Button>
|
||||||
|
<Button onClick={handleAddSubmit} variant="contained">Add User Detail</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Confirmation Dialog */}
|
||||||
|
<ConfirmModal
|
||||||
|
open={showConfirmModal}
|
||||||
|
onClose={() => setShowConfirmModal(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
title="Confirm Deletion"
|
||||||
|
message={`Are you sure you want to delete "${deleteItem.reportName || 'this report'}"? This action cannot be undone.`}
|
||||||
|
confirmText="Delete"
|
||||||
|
cancelText="Cancel"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Report Configuration Dialog */}
|
||||||
|
<Dialog open={showReportConfigModal} onClose={handleReportConfigClose} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle>Report Configuration</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<Select
|
||||||
|
label="Connection Name"
|
||||||
|
name="conn_name"
|
||||||
|
value={reportConfigData.conn_name}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
>
|
||||||
|
<MenuItem value="">Select Database Connection</MenuItem>
|
||||||
|
{databaseConnections.map((db, index) => (
|
||||||
|
<MenuItem key={index} value={db.name}>
|
||||||
|
{db.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Date Parameter Required"
|
||||||
|
name="date_param_req"
|
||||||
|
value={reportConfigData.date_param_req}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
>
|
||||||
|
<MenuItem value="">Select Option</MenuItem>
|
||||||
|
<MenuItem value="Yes">Yes</MenuItem>
|
||||||
|
<MenuItem value="No">No</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="SQL String"
|
||||||
|
name="sql_str"
|
||||||
|
value={reportConfigData.sql_str}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={5}
|
||||||
|
sx={{ fontFamily: 'monospace' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Column String (html)"
|
||||||
|
name="column_str"
|
||||||
|
value={reportConfigData.column_str}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<TextField
|
||||||
|
label="Standard Parameter String (html)"
|
||||||
|
name="std_param_html"
|
||||||
|
value={reportConfigData.std_param_html}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Adhoc Parameter String (html)"
|
||||||
|
name="adhoc_param_html"
|
||||||
|
value={reportConfigData.adhoc_param_html}
|
||||||
|
onChange={handleReportConfigChange}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleReportConfigClose}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleReportConfigUpdate}
|
||||||
|
variant="contained"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Update Configuration'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserDetailsView;
|
||||||
212
src/components/Dashboard/reports/reportrunnerall.js
Normal file
212
src/components/Dashboard/reports/reportrunnerall.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import { fetchAllReportsApi } from '../../../APIServices/ReportRunnerAPI';
|
||||||
|
|
||||||
|
const ReportRunnerAll = () => {
|
||||||
|
const [gridData, setGridData] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [rowSelected, setRowSelected] = useState(null);
|
||||||
|
const [modalDelete, setModalDelete] = useState(false);
|
||||||
|
const [reports, setReports] = useState([]);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAllReports();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchAllReports = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await fetchAllReportsApi();
|
||||||
|
console.log("Fetched all reports:", data);
|
||||||
|
|
||||||
|
setGridData(data);
|
||||||
|
setReports(data);
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No data available");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while fetching reports:", error);
|
||||||
|
setError("Error fetching data.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToAdd = () => navigate("/admin/user-report");
|
||||||
|
const goToAdd2 = () => navigate("/admin/reportbuild2all");
|
||||||
|
|
||||||
|
const goToRunner = (report) => {
|
||||||
|
console.log("at time of navigating reportID: ",report.id, " report: ",report);
|
||||||
|
if (report.isSql) {
|
||||||
|
navigate(`/admin/report-runner1/${report.id}`,{ state: { reportId: report.id, reportData: report}});
|
||||||
|
} else {
|
||||||
|
navigate(`/admin/report-runner2/${report.id}`,{ state: { reportId: report.id, reportData: report}});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
setModalDelete(false);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/report-builder/${id}`, { method: "DELETE" });
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success("Deleted successfully");
|
||||||
|
fetchAllReports();
|
||||||
|
} else {
|
||||||
|
toast.error("Error deleting data.");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Error deleting data.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDeleteModal = (row) => {
|
||||||
|
setRowSelected(row);
|
||||||
|
setModalDelete(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container-fluid p-4">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h3 className="m-0"><strong>All Reports</strong></h3>
|
||||||
|
<div>
|
||||||
|
<button className="btn btn-primary me-2" onClick={goToAdd}>
|
||||||
|
<i className="bi bi-plus me-1"></i> Report Builder SQL
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-primary" onClick={goToAdd2}>
|
||||||
|
<i className="bi bi-plus me-1"></i> Report Builder URL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Loading Spinner */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="alert alert-info d-flex align-items-center mt-3">
|
||||||
|
<div className="spinner-border me-2" role="status"></div>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Report Cards */}
|
||||||
|
{!isLoading && gridData.length > 0 && (
|
||||||
|
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
|
||||||
|
{gridData.map((report, index) => (
|
||||||
|
<div className="col" key={index}>
|
||||||
|
<div
|
||||||
|
className="card h-100 shadow-sm border-0"
|
||||||
|
onClick={() => goToRunner(report)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
{/* Card Header */}
|
||||||
|
<div
|
||||||
|
className={`card-header d-flex justify-content-between align-items-center ${report.isSql ? 'bg-primary bg-opacity-10' : 'bg-warning bg-opacity-10'}`}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className={`me-2 p-2 rounded ${report.isSql ? 'bg-primary bg-opacity-25' : 'bg-warning bg-opacity-25'}`}>
|
||||||
|
<i className={`bi ${report.isSql ? 'bi-database' : 'bi-link-45deg'} ${report.isSql ? 'text-primary' : 'text-warning'}`}></i>
|
||||||
|
</div>
|
||||||
|
<span className={`fw-semibold ${report.isSql ? 'text-primary' : 'text-warning'}`}>
|
||||||
|
{report.isSql == null ? "N/A" : report.isSql ? "SQL Report" : "URL Report"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className={`badge ${report.active ? 'bg-success' : 'bg-danger'}`}>
|
||||||
|
{report.active ? "Active" : "Inactive"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Body */}
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title fw-bold text-truncate" title={report.reportName}>
|
||||||
|
{report.reportName}
|
||||||
|
</h5>
|
||||||
|
<p
|
||||||
|
className="card-text text-muted"
|
||||||
|
style={{
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: '2',
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '42px'
|
||||||
|
}}
|
||||||
|
title={report.description}
|
||||||
|
>
|
||||||
|
{report.description || "No description available"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Footer */}
|
||||||
|
<div className="card-footer bg-light">
|
||||||
|
<div className="d-flex justify-content-between align-items-center">
|
||||||
|
<small className="text-muted">
|
||||||
|
<i className="bi bi-clock me-1"></i>
|
||||||
|
Updated: {new Date(report.updatedAt).toLocaleDateString()}
|
||||||
|
</small>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openDeleteModal(report);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bi bi-three-dots"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
{error && <div className="alert alert-danger mt-3">{error}</div>}
|
||||||
|
|
||||||
|
{/* Delete Modal */}
|
||||||
|
{modalDelete && (
|
||||||
|
<div className="modal show d-block" tabIndex="-1" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h5 className="modal-title">Delete Confirmation</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
onClick={() => setModalDelete(false)}
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p>Are you sure you want to delete the report?</p>
|
||||||
|
<h6>{rowSelected?.reportName}</h6>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={() => setModalDelete(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={() => handleDelete(rowSelected.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportRunnerAll;
|
||||||
@@ -45,7 +45,25 @@
|
|||||||
.sidebar li:hover {
|
.sidebar li:hover {
|
||||||
background-color: #555;
|
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 */
|
/* Responsive styles */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
|
|||||||
@@ -1,118 +1,192 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from 'react';
|
||||||
import { FiChevronRight, FiChevronDown, FiSettings } from "react-icons/fi";
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Link } from "react-router-dom";
|
import {
|
||||||
import { FaChevronRight as FaChevronRightIcon } from "react-icons/fa";
|
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 Sidebar = ({ menus, handleMenuItemClick, collapsed }) => {
|
const drawerWidth = 220;
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const collapsedWidth = 60;
|
||||||
const [openSubmenu, setOpenSubmenu] = useState(null);
|
|
||||||
const [menuItems, setMenuItems] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const Sidebar = ({ onSidebarToggle }) => {
|
||||||
const fetchMenuItems = async () => {
|
const theme = useTheme();
|
||||||
const token = localStorage.getItem("authToken");
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
try {
|
const [openTransaction, setOpenTransaction] = useState(false);
|
||||||
const res = await fetch(
|
const navigate = useNavigate();
|
||||||
`${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`,
|
|
||||||
{
|
const handleToggle = () => {
|
||||||
headers: {
|
setCollapsed(!collapsed);
|
||||||
Authorization: `Bearer ${token}`,
|
if (onSidebarToggle) {
|
||||||
},
|
onSidebarToggle(!collapsed);
|
||||||
}
|
|
||||||
);
|
|
||||||
const data = await res.json();
|
|
||||||
setMenuItems(data);
|
|
||||||
console.log("Fetched Menu Data:", data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching menu items:", error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMenuItems();
|
const handleTransactionToggle = () => {
|
||||||
}, []);
|
setOpenTransaction(!openTransaction);
|
||||||
|
|
||||||
const handleToggle = () => setIsOpen(!isOpen);
|
|
||||||
const handleSubmenuToggle = (menu) => {
|
|
||||||
setOpenSubmenu(openSubmenu === menu ? null : menu);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
padding: theme.spacing(0, 1),
|
||||||
|
...theme.mixins.toolbar,
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`flex flex-col h-screen bg-gray-100 text-black ${isOpen ? "w-64" : "w-20"
|
<Drawer
|
||||||
} transition-all duration-300`}
|
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'
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-4">
|
<DrawerHeader>
|
||||||
{isOpen ? (
|
<IconButton onClick={handleToggle} size="small">
|
||||||
<span className="text-lg font-bold">Menu</span>
|
{collapsed ? <ChevronRight /> : <ChevronLeft />}
|
||||||
) : (
|
</IconButton>
|
||||||
<FiSettings
|
</DrawerHeader>
|
||||||
className="text-2xl cursor-pointer"
|
<Divider />
|
||||||
onClick={handleToggle}
|
<List>
|
||||||
/>
|
{/* Transaction with submenus */}
|
||||||
)}
|
<ListItem disablePadding>
|
||||||
<button onClick={handleToggle}>
|
<ListItemButton
|
||||||
<svg
|
onClick={handleTransactionToggle}
|
||||||
className="w-6 h-6"
|
sx={{
|
||||||
fill="none"
|
minHeight: 48,
|
||||||
stroke="currentColor"
|
justifyContent: collapsed ? 'center' : 'initial',
|
||||||
viewBox="0 0 24 24"
|
px: 2.5,
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<path
|
<ListItemIcon
|
||||||
strokeLinecap="round"
|
sx={{
|
||||||
strokeLinejoin="round"
|
minWidth: 0,
|
||||||
strokeWidth="2"
|
mr: collapsed ? 'auto' : 3,
|
||||||
d="M4 6h16M4 12h16m-7 6h7"
|
justifyContent: 'center',
|
||||||
></path>
|
color: 'inherit'
|
||||||
</svg>
|
}}
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{isOpen && (
|
|
||||||
<nav className="flex flex-col mt-4">
|
|
||||||
{menuItems.map((item) => (
|
|
||||||
<div key={item.id} className="relative">
|
|
||||||
<button
|
|
||||||
className="flex items-center p-4 hover:bg-gray-300 w-full text-left"
|
|
||||||
onClick={() => handleSubmenuToggle(item.id)}
|
|
||||||
>
|
>
|
||||||
{item.icon && <item.icon className="w-6 h-6 mr-2" />}
|
<SwapHorizIcon />
|
||||||
<span>{item.menuItemDesc}</span>
|
</ListItemIcon>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<FaChevronRightIcon className="w-4 h-4 opacity-0 group-hover:opacity-100 transform group-hover:translate-x-1 transition-all duration-200" />
|
<>
|
||||||
|
<ListItemText primary="Transaction" />
|
||||||
|
{openTransaction ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</ListItemButton>
|
||||||
{/*
|
</ListItem>
|
||||||
<Link to={`${subItem.menuItemDesc}`} ><span className="ml-4">{subItem.menuItemDesc}</span> </Link>
|
|
||||||
*/}
|
|
||||||
{item.subMenus && openSubmenu === item.id && (
|
|
||||||
<ul className="pl-8 bg-gray-200">
|
|
||||||
{item.subMenus.map((subItem) => (
|
|
||||||
<Link to={`/${subItem.menuItemDesc}`} style={{ textDecoration: 'none', }}>
|
|
||||||
<li
|
|
||||||
key={subItem.id}
|
|
||||||
className="flex items-center p-2 hover:bg-gray-300"
|
|
||||||
>
|
|
||||||
<span className="ml-4">{subItem.menuItemDesc}</span>
|
|
||||||
|
|
||||||
{subItem.subMenus && (
|
{!collapsed && (
|
||||||
<div className="ml-auto">
|
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
|
||||||
{openSubmenu === subItem.id ? (
|
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||||
<FiChevronDown />
|
<ListItemButton
|
||||||
) : (
|
sx={{ pl: 4 }}
|
||||||
<FiChevronRight />
|
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
{/* 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" />
|
||||||
)}
|
)}
|
||||||
</li>
|
</ListItemButton>
|
||||||
</Link>
|
</ListItem>
|
||||||
))}
|
</List>
|
||||||
</ul>
|
</Drawer>
|
||||||
)}
|
|
||||||
</div>
|
{/* Main content spacer */}
|
||||||
))}
|
<Box
|
||||||
</nav>
|
component="main"
|
||||||
)}
|
sx={{
|
||||||
</div>
|
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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
308
src/components/Dashboard/sub menu/submenumaintanence.js
Normal file
308
src/components/Dashboard/sub menu/submenumaintanence.js
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
getSubmenuItems,
|
||||||
|
addSubmenuItem,
|
||||||
|
updateSubmenuItem,
|
||||||
|
deleteSubmenuItem
|
||||||
|
} from '../../../ApiServices/menumaintananceapi';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
CircularProgress
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Add as AddIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
MoreVert as MoreVertIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
const SubMenuMaintenance = () => {
|
||||||
|
const { menuItemId } = useParams();
|
||||||
|
const [subMenus, setSubMenus] = useState([]);
|
||||||
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
|
const [currentItem, setCurrentItem] = useState({
|
||||||
|
menuItemId: "",
|
||||||
|
menuItemDesc: "",
|
||||||
|
itemSeq: "",
|
||||||
|
moduleName: "",
|
||||||
|
main_menu_action_name: "#",
|
||||||
|
status: "Enable",
|
||||||
|
main_menu_icon_name: "fa-circle"
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSubMenus();
|
||||||
|
}, [menuItemId]);
|
||||||
|
|
||||||
|
const fetchSubMenus = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getSubmenuItems(menuItemId);
|
||||||
|
setSubMenus(data);
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Failed to fetch submenu items");
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = async () => {
|
||||||
|
try {
|
||||||
|
const addedItem = await addSubmenuItem(menuItemId, currentItem);
|
||||||
|
setSubMenus([...subMenus, addedItem]);
|
||||||
|
resetForm();
|
||||||
|
setShowAddModal(false);
|
||||||
|
toast.success("Submenu added successfully!");
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err.message || "Failed to add submenu");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (item) => {
|
||||||
|
setCurrentItem({
|
||||||
|
...item,
|
||||||
|
status: item.status ? "Enable" : "Disable"
|
||||||
|
});
|
||||||
|
setShowEditModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
try {
|
||||||
|
const updatedItem = await updateSubmenuItem(currentItem.menuItemId, currentItem);
|
||||||
|
setSubMenus(subMenus.map(item =>
|
||||||
|
item.menuItemId === updatedItem.menuItemId ? updatedItem : item
|
||||||
|
));
|
||||||
|
resetForm();
|
||||||
|
setShowEditModal(false);
|
||||||
|
toast.success("Submenu updated successfully!");
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err.message || "Failed to update submenu");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
if (window.confirm("Are you sure you want to delete this submenu?")) {
|
||||||
|
try {
|
||||||
|
await deleteSubmenuItem(id);
|
||||||
|
setSubMenus(subMenus.filter(item => item.menuItemId !== id));
|
||||||
|
toast.success("Submenu deleted successfully!");
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err.message || "Failed to delete submenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setCurrentItem({
|
||||||
|
menuItemId: "",
|
||||||
|
menuItemDesc: "",
|
||||||
|
itemSeq: "",
|
||||||
|
moduleName: "",
|
||||||
|
main_menu_action_name: "#",
|
||||||
|
status: "Enable",
|
||||||
|
main_menu_icon_name: "fa-circle"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 4 }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 4
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4" component="h2">
|
||||||
|
Sub-Menus for Menu ID: {menuItemId}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={() => setShowAddModal(true)}
|
||||||
|
>
|
||||||
|
ADD NEW
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>No.</TableCell>
|
||||||
|
<TableCell>Sub-Menu Item Name</TableCell>
|
||||||
|
<TableCell>ID</TableCell>
|
||||||
|
<TableCell>Sequence</TableCell>
|
||||||
|
<TableCell>Module Name</TableCell>
|
||||||
|
<TableCell>Menu Action Link</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{isLoading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={8} align="center">
|
||||||
|
<CircularProgress />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : subMenus.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={8} align="center">
|
||||||
|
No submenus found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
subMenus.map((item, index) => (
|
||||||
|
<TableRow key={item.menuItemId}>
|
||||||
|
<TableCell>{index + 1}</TableCell>
|
||||||
|
<TableCell>{item.menuItemDesc}</TableCell>
|
||||||
|
<TableCell>{item.menuItemId}</TableCell>
|
||||||
|
<TableCell>{item.itemSeq}</TableCell>
|
||||||
|
<TableCell>{item.moduleName}</TableCell>
|
||||||
|
<TableCell>{item.main_menu_action_name}</TableCell>
|
||||||
|
<TableCell>{item.status ? "Enable" : "Disable"}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton onClick={() => handleEdit(item)}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleDelete(item.menuItemId)}>
|
||||||
|
<DeleteIcon color="error" />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
{/* Add Submenu Modal */}
|
||||||
|
<Dialog open={showAddModal} onClose={() => setShowAddModal(false)}>
|
||||||
|
<DialogTitle>Add Submenu</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
label="Sub-Menu Item Name"
|
||||||
|
value={currentItem.menuItemDesc}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, menuItemDesc: e.target.value})}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Sequence"
|
||||||
|
type="number"
|
||||||
|
value={currentItem.itemSeq}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, itemSeq: e.target.value})}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Module Name"
|
||||||
|
value={currentItem.moduleName}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, moduleName: e.target.value})}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Menu Action Link"
|
||||||
|
value={currentItem.main_menu_action_name}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, main_menu_action_name: e.target.value})}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowAddModal(false)}>CANCEL</Button>
|
||||||
|
<Button onClick={handleAdd} variant="contained">ADD</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Edit Submenu Modal */}
|
||||||
|
<Dialog open={showEditModal} onClose={() => setShowEditModal(false)}>
|
||||||
|
<DialogTitle>Update Sub Menu Maintenance</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
label="Menu ID"
|
||||||
|
value={menuItemId}
|
||||||
|
disabled
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Menu Item Name"
|
||||||
|
value={currentItem.menuItemDesc}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, menuItemDesc: e.target.value})}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Sequence"
|
||||||
|
type="number"
|
||||||
|
value={currentItem.itemSeq}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, itemSeq: e.target.value})}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Module Name"
|
||||||
|
value={currentItem.moduleName}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, moduleName: e.target.value})}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Menu Action Link"
|
||||||
|
value={currentItem.main_menu_action_name}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, main_menu_action_name: e.target.value})}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={currentItem.status}
|
||||||
|
onChange={(e) => setCurrentItem({...currentItem, status: e.target.value})}
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
>
|
||||||
|
<MenuItem value="Enable">Enable</MenuItem>
|
||||||
|
<MenuItem value="Disable">Disable</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setShowEditModal(false)}>CANCEL</Button>
|
||||||
|
<Button onClick={handleUpdate} variant="contained">UPDATE</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubMenuMaintenance;
|
||||||
@@ -1,68 +1,18 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Eye, EyeOff, User, Lock, Mail, Camera, UserPlus, ArrowLeft, Check } from 'lucide-react';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { AccountCircle, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||||
// Mock navigation function for demo
|
import './Login.css'; // Import CSS file for custom styling
|
||||||
const mockNavigate = (path) => {
|
|
||||||
console.log(`Navigating to: ${path}`);
|
|
||||||
alert(`Would navigate to: ${path}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CreateAccountPage = () => {
|
const CreateAccountPage = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [reEnterPassword, setReEnterPassword] = useState('');
|
const [reEnterPassword, setReEnterPassword] = useState('');
|
||||||
const [avatarImage, setAvatarImage] = useState(null);
|
const [avatarImage, setAvatarImage] = useState(null);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const navigate = useNavigate();
|
||||||
const [showReEnterPassword, setShowReEnterPassword] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [passwordStrength, setPasswordStrength] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const handleCreateAccount = (e) => {
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Calculate password strength
|
|
||||||
let strength = 0;
|
|
||||||
if (password.length >= 8) strength++;
|
|
||||||
if (/[A-Z]/.test(password)) strength++;
|
|
||||||
if (/[0-9]/.test(password)) strength++;
|
|
||||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
|
||||||
setPasswordStrength(strength);
|
|
||||||
}, [password]);
|
|
||||||
|
|
||||||
const handleCreateAccount = async (e) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setErrorMessage('');
|
// Your create account logic here
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
if (!email || !password || !reEnterPassword) {
|
|
||||||
setErrorMessage('All fields are required.');
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password !== reEnterPassword) {
|
|
||||||
setErrorMessage('Passwords do not match.');
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 8) {
|
|
||||||
setErrorMessage('Password must be at least 8 characters long.');
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('Account created successfully!');
|
|
||||||
alert('Account created successfully!');
|
|
||||||
mockNavigate('/login');
|
|
||||||
setIsLoading(false);
|
|
||||||
}, 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAvatarChange = (e) => {
|
const handleAvatarChange = (e) => {
|
||||||
@@ -76,84 +26,20 @@ const CreateAccountPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPasswordStrengthColor = () => {
|
|
||||||
if (passwordStrength === 0) return 'bg-gray-300';
|
|
||||||
if (passwordStrength === 1) return 'bg-red-400';
|
|
||||||
if (passwordStrength === 2) return 'bg-yellow-400';
|
|
||||||
if (passwordStrength === 3) return 'bg-blue-400';
|
|
||||||
return 'bg-green-400';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPasswordStrengthText = () => {
|
|
||||||
if (passwordStrength === 0) return 'Enter password';
|
|
||||||
if (passwordStrength === 1) return 'Weak';
|
|
||||||
if (passwordStrength === 2) return 'Fair';
|
|
||||||
if (passwordStrength === 3) return 'Good';
|
|
||||||
return 'Strong';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-purple-100 relative overflow-hidden">
|
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
|
||||||
{/* Background Elements */}
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="absolute inset-0">
|
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
|
||||||
{/* Subtle geometric shapes */}
|
CLOUDNSURE
|
||||||
<div className="absolute top-20 left-20 w-32 h-32 bg-purple-200 rounded-full opacity-20 animate-pulse"></div>
|
|
||||||
<div className="absolute bottom-20 right-20 w-24 h-24 bg-purple-300 rounded-full opacity-20 animate-pulse" style={{ animationDelay: '1s' }}></div>
|
|
||||||
<div className="absolute top-1/2 right-10 w-16 h-16 bg-purple-400 rounded-full opacity-15 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
|
||||||
|
|
||||||
{/* Grid pattern */}
|
|
||||||
<div className="absolute inset-0 opacity-5" style={{
|
|
||||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%236B46C1' fill-opacity='0.4'%3E%3Cpath d='M20 20h20v20H20V20zm-20 0h20v20H0V20z'/%3E%3C/g%3E%3C/svg%3E")`
|
|
||||||
}}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
|
||||||
{/* Background Logo */}
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
||||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10">
|
||||||
{/* Create Account Card */}
|
<div className="flex items-center justify-center mb-6">
|
||||||
<div className={`relative w-full max-w-lg transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
<AccountCircle className="text-7xl text-gray-700" />
|
||||||
{/* Card Background */}
|
|
||||||
<div className="bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
|
||||||
{/* Header Section */}
|
|
||||||
<div className="bg-gradient-to-r from-purple-600 to-purple-700 p-8 text-center relative">
|
|
||||||
<div className="absolute top-4 left-4">
|
|
||||||
<button
|
|
||||||
onClick={() => mockNavigate('/login')}
|
|
||||||
className="p-2 text-white hover:bg-white hover:bg-opacity-20 rounded-xl transition-all duration-200"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Create Account</h2>
|
||||||
<div className="space-y-4">
|
<form onSubmit={handleCreateAccount} className="space-y-4">
|
||||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-white bg-opacity-20 rounded-2xl">
|
<div className="flex items-center justify-center">
|
||||||
<UserPlus className="w-8 h-8 text-white" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">Create Account</h1>
|
|
||||||
<p className="text-purple-100">Join our community today</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Form Section */}
|
|
||||||
<div className="p-8 space-y-6">
|
|
||||||
{/* Error Message */}
|
|
||||||
{errorMessage && (
|
|
||||||
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm">
|
|
||||||
{errorMessage}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Avatar Upload */}
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="relative inline-block">
|
|
||||||
<input
|
<input
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
id="avatar-input"
|
id="avatar-input"
|
||||||
@@ -161,171 +47,61 @@ const CreateAccountPage = () => {
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleAvatarChange}
|
onChange={handleAvatarChange}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="avatar-input" className="cursor-pointer group">
|
<label htmlFor="avatar-input" className="cursor-pointer">
|
||||||
<div className="relative">
|
|
||||||
<img
|
<img
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
src={avatarImage || 'https://via.placeholder.com/120/E5E7EB/6B7280?text=Photo'}
|
src={avatarImage || 'https://via.placeholder.com/120'}
|
||||||
className="w-28 h-28 rounded-full border-4 border-purple-100 group-hover:border-purple-300 transition-all duration-200 object-cover"
|
className="w-28 h-28 rounded-full mb-4"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 rounded-full transition-all duration-200 flex items-center justify-center">
|
|
||||||
<Camera className="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 mt-2">Click to upload your photo</p>
|
<div>
|
||||||
</div>
|
<label className="block text-gray-600">Email</label>
|
||||||
|
|
||||||
{/* Form Fields */}
|
|
||||||
<div className="space-y-5">
|
|
||||||
{/* Email Field */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Email Address</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Mail className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
placeholder="Enter your email"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
<label className="block text-gray-600">Password</label>
|
||||||
{/* Password Field */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Password</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full pl-12 pr-12 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
placeholder="Create a strong password"
|
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Password Strength Indicator */}
|
|
||||||
{password && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className="flex-1 bg-gray-200 rounded-full h-2">
|
|
||||||
<div
|
|
||||||
className={`h-2 rounded-full transition-all duration-300 ${getPasswordStrengthColor()}`}
|
|
||||||
style={{ width: `${(passwordStrength / 4) * 100}%` }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-medium text-gray-600">
|
|
||||||
{getPasswordStrengthText()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-500 space-y-1">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className={`w-2 h-2 rounded-full ${password.length >= 8 ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
||||||
<span>At least 8 characters</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className={`w-2 h-2 rounded-full ${/[A-Z]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
||||||
<span>One uppercase letter</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className={`w-2 h-2 rounded-full ${/[0-9]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
||||||
<span>One number</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Re-enter Password Field */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Confirm Password</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-600">Re-enter Password</label>
|
||||||
<input
|
<input
|
||||||
type={showReEnterPassword ? 'text' : 'password'}
|
type="password"
|
||||||
value={reEnterPassword}
|
value={reEnterPassword}
|
||||||
onChange={(e) => setReEnterPassword(e.target.value)}
|
onChange={(e) => setReEnterPassword(e.target.value)}
|
||||||
className={`w-full pl-12 pr-12 py-4 bg-gray-50 border rounded-xl focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 ${
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
reEnterPassword && password !== reEnterPassword
|
|
||||||
? 'border-red-300 focus:ring-red-500'
|
|
||||||
: reEnterPassword && password === reEnterPassword
|
|
||||||
? 'border-green-300 focus:ring-green-500'
|
|
||||||
: 'border-gray-200 focus:ring-purple-500'
|
|
||||||
}`}
|
|
||||||
placeholder="Confirm your password"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="submit"
|
||||||
onClick={() => setShowReEnterPassword(!showReEnterPassword)}
|
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"
|
||||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
||||||
>
|
>
|
||||||
{showReEnterPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
Create Account
|
||||||
</button>
|
|
||||||
{reEnterPassword && password === reEnterPassword && (
|
|
||||||
<div className="absolute right-12 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Check className="w-5 h-5 text-green-500" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{reEnterPassword && password !== reEnterPassword && (
|
|
||||||
<p className="text-xs text-red-500">Passwords do not match</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Create Account Button */}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleCreateAccount}
|
|
||||||
disabled={isLoading || !email || !password || !reEnterPassword || password !== reEnterPassword}
|
|
||||||
className="w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-purple-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center space-x-2">
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
||||||
<span>Creating Account...</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<UserPlus className="w-5 h-5" />
|
|
||||||
<span>Create Account</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Login Link */}
|
|
||||||
<div className="text-center pt-4 border-t border-gray-100">
|
|
||||||
<p className="text-gray-600 mb-3">Already have an account?</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => mockNavigate('/login')}
|
|
||||||
className="inline-flex items-center space-x-2 text-purple-600 hover:text-purple-700 font-semibold transition-colors"
|
|
||||||
>
|
|
||||||
<User className="w-4 h-4" />
|
|
||||||
<span>Sign In</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,191 +1,96 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Mail, ArrowLeft, Shield, CheckCircle, AlertCircle, Sparkles } from 'lucide-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 ForgotPasswordPage = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [messageType, setMessageType] = useState('');
|
const [messageType, setMessageType] = useState(''); // State to manage message type for styling
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const navigate = useNavigate();
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [particles, setParticles] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
|
|
||||||
const newParticles = Array.from({ length: 35 }, (_, i) => ({
|
|
||||||
id: i,
|
|
||||||
x: Math.random() * 100,
|
|
||||||
y: Math.random() * 100,
|
|
||||||
size: Math.random() * 3 + 1,
|
|
||||||
duration: Math.random() * 4 + 3,
|
|
||||||
delay: Math.random() * 2,
|
|
||||||
}));
|
|
||||||
setParticles(newParticles);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
|
||||||
setMessage('');
|
|
||||||
setMessageType('');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (email.includes('@')) {
|
|
||||||
setMessage('Reset password email sent successfully. Please check your email.');
|
setMessage('Reset password email sent successfully. Please check your email.');
|
||||||
setMessageType('success');
|
setMessageType('success');
|
||||||
setEmail('');
|
|
||||||
} else {
|
|
||||||
setMessage('Please enter a valid email address.');
|
|
||||||
setMessageType('error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage(`Error during reset password: ${error.message}`);
|
setMessage(`Error during reset password: ${error.message}`);
|
||||||
setMessageType('error');
|
setMessageType('error');
|
||||||
} finally {
|
console.error('Error during reset password:', error);
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackToLogin = () => {
|
|
||||||
alert('Back to login functionality would be implemented here');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
|
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
|
||||||
<div className="absolute inset-0">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
|
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
|
||||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
CLOUDNSURE
|
||||||
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
|
|
||||||
|
|
||||||
{particles.map((particle) => (
|
|
||||||
<div
|
|
||||||
key={particle.id}
|
|
||||||
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
|
|
||||||
style={{
|
|
||||||
left: `${particle.x}%`,
|
|
||||||
top: `${particle.y}%`,
|
|
||||||
width: `${particle.size}px`,
|
|
||||||
height: `${particle.size}px`,
|
|
||||||
animationDuration: `${particle.duration}s`,
|
|
||||||
animationDelay: `${particle.delay}s`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="absolute inset-0 opacity-20" style={{
|
|
||||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
|
|
||||||
}}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
||||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
|
||||||
<div className="flex justify-center mt-4 space-x-2">
|
|
||||||
{[...Array(5)].map((_, i) => (
|
|
||||||
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
|
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Forgot Password</h2>
|
||||||
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
<p className="text-center text-gray-600 mb-4">
|
||||||
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
Enter your email address and we'll send you a link to reset your password.
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
|
</p>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="relative p-8 space-y-6">
|
|
||||||
<button
|
|
||||||
onClick={handleBackToLogin}
|
|
||||||
className="group absolute top-6 left-6 p-2 text-gray-400 hover:text-purple-600 transition-colors rounded-full hover:bg-purple-50"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="text-center space-y-4 pt-8">
|
|
||||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
|
|
||||||
<Shield className="w-10 h-10 text-white" />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">Forgot Password?</h1>
|
<label className="block text-gray-600">Email</label>
|
||||||
<p className="text-gray-600 leading-relaxed">Don't worry! Enter your email address and we'll send you a secure link to reset your password.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{message && (
|
|
||||||
<div className={`rounded-xl p-4 text-sm animate-pulse flex items-center space-x-3 ${
|
|
||||||
messageType === 'error'
|
|
||||||
? 'bg-red-50 border border-red-200 text-red-700'
|
|
||||||
: 'bg-green-50 border border-green-200 text-green-700'
|
|
||||||
}`}>
|
|
||||||
{messageType === 'error' ? (
|
|
||||||
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<CheckCircle className="w-5 h-5 flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span>{message}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Email Address</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
|
||||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Mail className="w-5 h-5 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
placeholder="Enter your email address"
|
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-center">
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmit}
|
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"
|
||||||
disabled={isLoading}
|
|
||||||
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
|
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
Reset Password
|
||||||
<div className="relative flex items-center justify-center space-x-2">
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
||||||
<span>Sending Reset Link...</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Mail className="w-5 h-5" />
|
|
||||||
<span>Send Reset Link</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div className="text-center pt-4 border-t border-gray-200">
|
{message && (
|
||||||
<p className="text-gray-600 mb-3">Remember your password?</p>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleBackToLogin}
|
onClick={() => navigate('/login')}
|
||||||
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
|
className="text-blue-600 hover:underline focus:outline-none"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4 text-purple-600 group-hover:text-purple-700 group-hover:-translate-x-1 transition-all" />
|
Log in
|
||||||
<span>Back to Sign In</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,55 +1,61 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Eye, EyeOff, User, Lock, Mail, Sparkles, Shield, ArrowRight } from 'lucide-react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 LoginPage = () => {
|
||||||
const navigate = useNavigate();
|
const [email, setEmail] = useState('');
|
||||||
const [username, setUsername] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [rememberMe, setRememberMe] = useState(false);
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const navigate = useNavigate();
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [particles, setParticles] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
|
|
||||||
const newParticles = Array.from({ length: 40 }, (_, i) => ({
|
|
||||||
id: i,
|
|
||||||
x: Math.random() * 100,
|
|
||||||
y: Math.random() * 100,
|
|
||||||
size: Math.random() * 3 + 1,
|
|
||||||
duration: Math.random() * 4 + 3,
|
|
||||||
delay: Math.random() * 2,
|
|
||||||
}));
|
|
||||||
setParticles(newParticles);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLogin = async (e) => {
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
if (!username || !password) {
|
if (!email || !password) {
|
||||||
setErrorMessage('Username and password are required.');
|
setErrorMessage('Email and password are required.');
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedCredentials = JSON.parse(localStorage.getItem('userCredentials') || '{}');
|
try {
|
||||||
|
const response = await fetch(API_TOKEN_SESSION, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email, password }),
|
||||||
|
});
|
||||||
|
|
||||||
if ((username === 'sysadmin' && password === 'test3') ||
|
if (!response.ok) {
|
||||||
(storedCredentials[username] && storedCredentials[username] === password)) {
|
const errorText = await response.text();
|
||||||
localStorage.setItem('authToken', 'dummy-token');
|
console.error('Login failed:', errorText);
|
||||||
localStorage.setItem('currentUser', username);
|
setErrorMessage('Login failed. Please check your credentials.');
|
||||||
navigate('/Dashboard');
|
return;
|
||||||
} else {
|
|
||||||
setErrorMessage('Invalid username or password. Please use sysadmin/test3 or your registered credentials.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
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 = () => {
|
const handleForgotPassword = () => {
|
||||||
@@ -61,174 +67,86 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
|
<div className="relative min-h-screen flex items-center justify-center bg-gray-700">
|
||||||
<div className="absolute inset-0">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
|
<div className="text-center text-gray-100 text-9xl font-bold opacity-75">
|
||||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
CLOUDNSURE
|
||||||
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
|
|
||||||
|
|
||||||
{particles.map((particle) => (
|
|
||||||
<div
|
|
||||||
key={particle.id}
|
|
||||||
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
|
|
||||||
style={{
|
|
||||||
left: `${particle.x}%`,
|
|
||||||
top: `${particle.y}%`,
|
|
||||||
width: `${particle.size}px`,
|
|
||||||
height: `${particle.size}px`,
|
|
||||||
animationDuration: `${particle.duration}s`,
|
|
||||||
animationDelay: `${particle.delay}s`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="absolute inset-0 opacity-20" style={{
|
|
||||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
|
|
||||||
}}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
||||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
|
||||||
<div className="flex justify-center mt-4 space-x-2">
|
|
||||||
{[...Array(5)].map((_, i) => (
|
|
||||||
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
|
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4 ">Log in</h2>
|
||||||
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
{errorMessage && <div className="mb-4 text-red-600">{errorMessage}</div>}
|
||||||
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
<form onSubmit={handleLogin} className="space-y-4">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
|
|
||||||
|
|
||||||
<div className="relative p-8 space-y-6">
|
|
||||||
<div className="text-center space-y-4">
|
|
||||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
|
|
||||||
<Shield className="w-10 h-10 text-white" />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">Welcome Back</h1>
|
<label className="block text-gray-600">Email</label>
|
||||||
<p className="text-gray-600">Sign in to your account</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errorMessage && (
|
|
||||||
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm animate-pulse">
|
|
||||||
{errorMessage}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Username</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
|
||||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<User className="w-5 h-5 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={email}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
placeholder="Enter your username"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<label className="block text-gray-600">Password</label>
|
||||||
|
<div className="relative">
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">Password</label>
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
|
||||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
|
||||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
||||||
<Lock className="w-5 h-5 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full pl-12 pr-12 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||||
placeholder="Enter your password"
|
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-purple-600 transition-colors"
|
className=""
|
||||||
>
|
>
|
||||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="flex items-center space-x-3 cursor-pointer group">
|
<label className="flex items-center">
|
||||||
<div className="relative">
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={rememberMe}
|
checked={rememberMe}
|
||||||
onChange={(e) => setRememberMe(e.target.checked)}
|
onChange={(e) => setRememberMe(e.target.checked)}
|
||||||
className="sr-only"
|
className="form-checkbox"
|
||||||
/>
|
/>
|
||||||
<div className={`w-5 h-5 rounded border-2 transition-all ${rememberMe ? 'bg-gradient-to-r from-purple-500 to-indigo-600 border-purple-500' : 'border-gray-300 group-hover:border-purple-400'}`}>
|
<span className="ml-2 text-gray-600">Remember Me</span>
|
||||||
{rememberMe && (
|
|
||||||
<svg className="w-3 h-3 text-white absolute top-0.5 left-0.5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-gray-600 group-hover:text-purple-700 transition-colors">Remember me</span>
|
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleForgotPassword}
|
onClick={handleForgotPassword}
|
||||||
className="text-sm text-purple-600 hover:text-purple-700 transition-colors font-medium"
|
className="text-sm text-blue-600 hover:underline focus:outline-none"
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="submit"
|
||||||
onClick={handleLogin}
|
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"
|
||||||
disabled={isLoading}
|
|
||||||
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
|
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
Login
|
||||||
<div className="relative flex items-center justify-center space-x-2">
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
||||||
<span>Signing in...</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span>Sign In</span>
|
|
||||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div className="text-center pt-4 border-t border-gray-200">
|
<div className="mt-4 text-center">
|
||||||
<p className="text-gray-600 mb-3">Don't have an account?</p>
|
<p className="text-gray-600">
|
||||||
|
Don't have an account?{' '}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCreateAccount}
|
onClick={handleCreateAccount}
|
||||||
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
|
className="text-blue-600 hover:underline focus:outline-none"
|
||||||
>
|
>
|
||||||
<User className="w-4 h-4 text-purple-600 group-hover:text-purple-700" />
|
Create Account
|
||||||
<span>Create New Account</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
106
src/components/common/ConfirmModel.js
Normal file
106
src/components/common/ConfirmModel.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Box
|
||||||
|
} from '@mui/material';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable confirmation modal component using Material-UI
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {boolean} props.open - Controls modal visibility
|
||||||
|
* @param {Function} props.onClose - Function to call when modal is closed
|
||||||
|
* @param {Function} props.onConfirm - Function to call when action is confirmed
|
||||||
|
* @param {string} props.title - Modal title
|
||||||
|
* @param {string} props.message - Confirmation message
|
||||||
|
* @param {string} props.confirmLabel - Label for the confirm button
|
||||||
|
* @param {string} props.cancelLabel - Label for the cancel button
|
||||||
|
* @param {string} props.confirmColor - Color for the confirm button ('primary', 'secondary', 'error', etc.)
|
||||||
|
*/
|
||||||
|
const ConfirmModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
title = "Confirm Action",
|
||||||
|
message = "Are you sure you want to proceed?",
|
||||||
|
confirmLabel = "Confirm",
|
||||||
|
cancelLabel = "Cancel",
|
||||||
|
confirmColor = "error"
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: 2
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: 'primary.main',
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider'
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
<IconButton
|
||||||
|
onClick={onClose}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
'&:hover': {
|
||||||
|
color: 'text.primary'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="close"
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent sx={{ pt: 3 }}>
|
||||||
|
<Typography variant="body1">{message}</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions sx={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
px: 3,
|
||||||
|
py: 2,
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderColor: 'divider'
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{ px: 4 }}
|
||||||
|
>
|
||||||
|
{cancelLabel}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color={confirmColor}
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
sx={{ px: 4 }}
|
||||||
|
>
|
||||||
|
{confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmModal;
|
||||||
159
src/components/dashboardnew/dashboardadd/dashboardbuilderadd.js
Normal file
159
src/components/dashboardnew/dashboardadd/dashboardbuilderadd.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Checkbox,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Chip
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
Save as SaveIcon,
|
||||||
|
ArrowBack as ArrowBackIcon
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
|
||||||
|
|
||||||
|
const DashboardbuilderAdd = () => {
|
||||||
|
const [moduleId, setModuleId] = useState(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { register, handleSubmit, formState: { errors } } = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
dashboard_name: "",
|
||||||
|
description: "",
|
||||||
|
secuirity_profile: "",
|
||||||
|
module_id: null,
|
||||||
|
tech_Stack: "",
|
||||||
|
object_type: "",
|
||||||
|
sub_object_type: "",
|
||||||
|
add_to_home: true,
|
||||||
|
dashbord1_Line: [{
|
||||||
|
model: JSON.stringify({
|
||||||
|
dashboard: [{
|
||||||
|
cols: 4,
|
||||||
|
rows: 5,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
name: "Radar Chart",
|
||||||
|
component: "Radar Chart",
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setModuleId("exampleModuleId");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
if (!data.dashboard_name || !data.secuirity_profile || !data.description) {
|
||||||
|
toast.error("Please fill out all required fields.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDashboard = {
|
||||||
|
...data,
|
||||||
|
};
|
||||||
|
|
||||||
|
DashboardBuilderService.create(newDashboard)
|
||||||
|
.then((response) => {
|
||||||
|
toast.success("Dashboard Added successfully");
|
||||||
|
navigate("/dashboard/dashboard-new-all");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Error while adding dashboard");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card sx={{ p: 3 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 500 }}>
|
||||||
|
Define Dashboard
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label="Add Mode"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 3, mb: 3 }}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
label="Dashboard Name"
|
||||||
|
variant="outlined"
|
||||||
|
{...register("dashboard_name", { required: true })}
|
||||||
|
error={!!errors.dashboard_name}
|
||||||
|
helperText={errors.dashboard_name ? "This field is required" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
label="Security Profile"
|
||||||
|
variant="outlined"
|
||||||
|
{...register("secuirity_profile", { required: true })}
|
||||||
|
error={!!errors.secuirity_profile}
|
||||||
|
helperText={errors.secuirity_profile ? "This field is required" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth sx={{ gridColumn: '1 / -1' }}>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
variant="outlined"
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
{...register("description", { required: true })}
|
||||||
|
error={!!errors.description}
|
||||||
|
helperText={errors.description ? "This field is required" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked
|
||||||
|
{...register("add_to_home")}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Add to Dashboard"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
onClick={() => navigate("/dashboard/dashboard-new-all")}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardbuilderAdd;
|
||||||
213
src/components/dashboardnew/dashboardbuildernewall.js
Normal file
213
src/components/dashboardnew/dashboardbuildernewall.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Typography,
|
||||||
|
CircularProgress,
|
||||||
|
Alert,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
Tooltip
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Add as AddIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
GridOn as GridIcon,
|
||||||
|
FileDownload as ExportIcon,
|
||||||
|
Schedule as TimeIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import DashboardBuilderService from "../../../src/ApiServices/DashboardBuilderServices";
|
||||||
|
import ConfirmModal from "../common/ConfirmModel";
|
||||||
|
|
||||||
|
const DashboardNewAll = () => {
|
||||||
|
const { t: translate } = useTranslation();
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||||
|
const [selectedRow, setSelectedRow] = useState(null);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAllDashboards();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchAllDashboards = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const dashboards = await DashboardBuilderService.getAllDash();
|
||||||
|
setData(dashboards);
|
||||||
|
toast.success("Dashboards fetched successfully.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching dashboards:', error);
|
||||||
|
toast.error('Failed to fetch dashboards.');
|
||||||
|
setError(error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddClick = () => {
|
||||||
|
navigate("/dashboard/dashboard-new-add");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRunner = () => {
|
||||||
|
navigate("/dashboard/dashboard-runner-all");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditClick = (dashboard) => {
|
||||||
|
navigate(`/dashboard/dashboard-new-edit/${dashboard.id}`, { state: { dashboard } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteClick = (row) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
setShowConfirmModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
try {
|
||||||
|
await DashboardBuilderService.deleteField(selectedRow.id);
|
||||||
|
setData(prevData => prevData.filter(dashboard => dashboard.id !== selectedRow.id));
|
||||||
|
toast.success("Dashboard deleted successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting dashboard field:", error);
|
||||||
|
toast.error("Failed to delete dashboard. Please try again.");
|
||||||
|
}
|
||||||
|
setShowConfirmModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExport = () => {
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(data);
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dashboards");
|
||||||
|
XLSX.writeFile(workbook, "dashboards_export.xlsx");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetUp = (id) => {
|
||||||
|
navigate(`/dashboard/edit-new-dash/${id}`, { dashboardId: id });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
||||||
|
<Typography variant="h4">{translate("Dashboard Builder")}</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<GridIcon />}
|
||||||
|
onClick={handleRunner}
|
||||||
|
>
|
||||||
|
{translate("Dashboard_runner")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ExportIcon />}
|
||||||
|
onClick={handleExport}
|
||||||
|
>
|
||||||
|
{translate("EXPORT_XLSX")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={handleAddClick}
|
||||||
|
>
|
||||||
|
{translate("ADD")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center", my: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : error ? (
|
||||||
|
<Alert severity="error" sx={{ mb: 3 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
) : data.length === 0 ? (
|
||||||
|
<Alert severity="info">No dashboards available.</Alert>
|
||||||
|
) : (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Go To</TableCell>
|
||||||
|
<TableCell>Dashboard Name</TableCell>
|
||||||
|
<TableCell>Description</TableCell>
|
||||||
|
<TableCell>Security Profile</TableCell>
|
||||||
|
<TableCell>Add to Home</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((dashboard) => (
|
||||||
|
<TableRow key={dashboard.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSetUp(dashboard.id)}
|
||||||
|
>
|
||||||
|
SET UP
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{dashboard.dashboard_name}</TableCell>
|
||||||
|
<TableCell>{dashboard.description}</TableCell>
|
||||||
|
<TableCell>{dashboard.secuirity_profile}</TableCell>
|
||||||
|
<TableCell>{dashboard.add_to_home ? "Yes" : "No"}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Tooltip title="Edit">
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={() => handleEditClick(dashboard)}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Delete">
|
||||||
|
<IconButton
|
||||||
|
color="error"
|
||||||
|
onClick={() => handleDeleteClick(dashboard)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
open={showConfirmModal}
|
||||||
|
onClose={() => setShowConfirmModal(false)}
|
||||||
|
onConfirm={handleDeleteConfirm}
|
||||||
|
title="Delete Dashboard"
|
||||||
|
message={`Are you sure you want to delete "${selectedRow?.dashboard_name || 'this dashboard'}"?`}
|
||||||
|
confirmText="Delete"
|
||||||
|
cancelText="Cancel"
|
||||||
|
confirmColor="error"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardNewAll;
|
||||||
278
src/components/dashboardnew/dashboardrunner/dashboardrunner.js
Normal file
278
src/components/dashboardnew/dashboardrunner/dashboardrunner.js
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CircularProgress,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { ArrowBack as ArrowBackIcon } from '@mui/icons-material';
|
||||||
|
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
|
||||||
|
|
||||||
|
// Import chart components
|
||||||
|
import LineChartComponent from '../gadgets/linechart';
|
||||||
|
import PieChartComponent from '../gadgets/piechart';
|
||||||
|
import PolarChartComponent from '../gadgets/polarchart';
|
||||||
|
import RadarChartComponent from '../gadgets/radarchart';
|
||||||
|
import BubbleChart from '../gadgets/bubblechart';
|
||||||
|
import BarChart from '../gadgets/barchart';
|
||||||
|
import DoughnutChart from '../gadgets/doughnut';
|
||||||
|
import DynamicChart from '../gadgets/dynamicchart';
|
||||||
|
import FinancialChart from '../gadgets/financialchart';
|
||||||
|
import GridViewComponent from '../gadgets/gridview';
|
||||||
|
import ScatterChartComponent from '../gadgets/scatterchart';
|
||||||
|
import ToDoChartComponent from '../gadgets/todochart';
|
||||||
|
|
||||||
|
const ViewDashboard = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [dashboard, setDashboard] = useState(null);
|
||||||
|
const [layout, setLayout] = useState([]);
|
||||||
|
|
||||||
|
const componentMap = {
|
||||||
|
'radar_chart': RadarChartComponent,
|
||||||
|
'polar_chart': PolarChartComponent,
|
||||||
|
'pie_chart': PieChartComponent,
|
||||||
|
'bar_chart': BarChart,
|
||||||
|
'bubble_chart': BubbleChart,
|
||||||
|
'line_chart': LineChartComponent,
|
||||||
|
'doughnut_chart': DoughnutChart,
|
||||||
|
'dynamic_chart': DynamicChart,
|
||||||
|
'financial_chart': FinancialChart,
|
||||||
|
'grid_view': GridViewComponent,
|
||||||
|
'scatter_chart': ScatterChartComponent,
|
||||||
|
'todo_chart': ToDoChartComponent
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDashboard = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await DashboardBuilderService.getById(id);
|
||||||
|
|
||||||
|
setDashboard({
|
||||||
|
name: response.dashboard_name,
|
||||||
|
dashboardLine: response.dashbord1_Line
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.dashbord1_Line && response.dashbord1_Line[0]?.model) {
|
||||||
|
const modelData = response.dashbord1_Line[0].model;
|
||||||
|
const parsedModel = JSON.parse(modelData);
|
||||||
|
|
||||||
|
if (Array.isArray(parsedModel.dashboard)) {
|
||||||
|
const dashboardLayout = parsedModel.dashboard.map((widget, index) => {
|
||||||
|
const componentName = widget.component || "Unknown";
|
||||||
|
const typeIdentifier = componentName.toLowerCase().replace(' ', '_');
|
||||||
|
const Component = componentMap[typeIdentifier] || null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `widget-${index}`,
|
||||||
|
x: widget.x || 0,
|
||||||
|
y: widget.y || 0,
|
||||||
|
w: widget.cols || 6,
|
||||||
|
h: widget.rows || 8,
|
||||||
|
name: widget.name || componentName,
|
||||||
|
component: Component,
|
||||||
|
chartTitle: widget.chartTitle || widget.name || componentName,
|
||||||
|
showLegend: widget.showLegend !== undefined ? widget.showLegend : true,
|
||||||
|
showLabel: widget.showLabel !== undefined ? widget.showLabel : true,
|
||||||
|
xAxis: widget.xAxis || 'Month',
|
||||||
|
yAxis: widget.yAxis || 'Value'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayout(dashboardLayout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching dashboard:", error);
|
||||||
|
setError("Failed to load dashboard. Please try again later.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchDashboard();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const getWidgetStyle = (widget) => {
|
||||||
|
const columnWidth = 100 / 6;
|
||||||
|
const rowHeight = 40;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${widget.x * columnWidth}%`,
|
||||||
|
top: `${widget.y * rowHeight}px`,
|
||||||
|
width: `${widget.w * columnWidth}%`,
|
||||||
|
height: `${widget.h * rowHeight}px`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: theme.palette.background.default
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="h6" sx={{ ml: 2 }}>
|
||||||
|
Loading Dashboard...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: theme.palette.background.default
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" color="error" gutterBottom>
|
||||||
|
Error Loading Dashboard
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">{error}</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
padding: 3,
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
minHeight: '100vh'
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 3
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4" color="text.primary">
|
||||||
|
{dashboard?.name || 'Dashboard View'}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{layout.length === 0 ? (
|
||||||
|
<Card sx={{ textAlign: 'center', p: 5 }}>
|
||||||
|
<Typography variant="h6">No widgets found in this dashboard.</Typography>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
minHeight: '800px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{layout.map((widget) => {
|
||||||
|
const Component = widget.component;
|
||||||
|
const style = getWidgetStyle(widget);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={widget.id}
|
||||||
|
sx={{
|
||||||
|
...style,
|
||||||
|
padding: 1,
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card sx={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
boxShadow: theme.shadows[2]
|
||||||
|
}}>
|
||||||
|
<CardHeader
|
||||||
|
title={widget.chartTitle}
|
||||||
|
titleTypographyProps={{ variant: 'h6' }}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CardContent sx={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
p: 2,
|
||||||
|
pt: 1,
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{Component ? (
|
||||||
|
<Box sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}>
|
||||||
|
<Component
|
||||||
|
chartTitle={widget.chartTitle}
|
||||||
|
showLegend={widget.showLegend}
|
||||||
|
showLabel={widget.showLabel}
|
||||||
|
xAxis={widget.xAxis}
|
||||||
|
yAxis={widget.yAxis}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100%'
|
||||||
|
}}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Widget type "{widget.name}" not available
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewDashboard;
|
||||||
@@ -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;
|
||||||
548
src/components/dashboardnew/editdashboard/editdashboard.js
Normal file
548
src/components/dashboardnew/editdashboard/editdashboard.js
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Checkbox,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
DragIndicator as DragIndicatorIcon,
|
||||||
|
Edit as EditIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Add as AddIcon,
|
||||||
|
Cancel as CancelIcon,
|
||||||
|
Save as SaveIcon,
|
||||||
|
ArrowBack as ArrowBackIcon
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { Responsive, WidthProvider } from 'react-grid-layout';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import DashboardBuilderService from "../../../ApiServices/DashboardBuilderServices";
|
||||||
|
|
||||||
|
// Import chart components
|
||||||
|
import LineChartComponent from '../gadgets/linechart';
|
||||||
|
import PieChartComponent from '../gadgets/piechart';
|
||||||
|
import PolarChartComponent from '../gadgets/polarchart';
|
||||||
|
import RadarChartComponent from '../gadgets/radarchart';
|
||||||
|
import BubbleChart from '../gadgets/bubblechart';
|
||||||
|
import BarChart from '../gadgets/barchart';
|
||||||
|
import DoughnutChart from '../gadgets/doughnut';
|
||||||
|
import DynamicChart from '../gadgets/dynamicchart';
|
||||||
|
import FinancialChart from '../gadgets/financialchart';
|
||||||
|
import GridViewComponent from '../gadgets/gridview';
|
||||||
|
import ScatterChartComponent from '../gadgets/scatterchart';
|
||||||
|
import ToDoChartComponent from '../gadgets/todochart';
|
||||||
|
|
||||||
|
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||||
|
|
||||||
|
const EditNewDash = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
|
const [currentEditWidget, setCurrentEditWidget] = useState({});
|
||||||
|
const [dashboardName, setDashboardName] = useState('');
|
||||||
|
const [dashboardLine, setDashboardLine] = useState([]);
|
||||||
|
const [dashboardlineId, setDashboardlineId] = useState(null);
|
||||||
|
const [dashboardWidgets, setDashboardWidgets] = useState([]);
|
||||||
|
|
||||||
|
const availableWidgets = [
|
||||||
|
{ identifier: 'radar_chart', name: 'Radar Chart' },
|
||||||
|
{ identifier: 'polar_chart', name: 'Polar Chart' },
|
||||||
|
{ identifier: 'pie_chart', name: 'Pie Chart' },
|
||||||
|
{ identifier: 'bar_chart', name: 'Bar Chart' },
|
||||||
|
{ identifier: 'bubble_chart', name: 'Bubble Chart' },
|
||||||
|
{ identifier: 'line_chart', name: 'Line Chart' },
|
||||||
|
{ identifier: 'doughnut_chart', name: 'Doughnut Chart' },
|
||||||
|
{ identifier: 'dynamic_chart', name: 'Dynamic Chart' },
|
||||||
|
{ identifier: 'financial_chart', name: 'Financial Chart' },
|
||||||
|
{ identifier: 'grid_view', name: 'Grid View' },
|
||||||
|
{ identifier: 'scatter_chart', name: 'Scatter Chart' },
|
||||||
|
{ identifier: 'todo_chart', name: 'Todo Chart' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const componentMap = {
|
||||||
|
'radar_chart': RadarChartComponent,
|
||||||
|
'polar_chart': PolarChartComponent,
|
||||||
|
'pie_chart': PieChartComponent,
|
||||||
|
'bar_chart': BarChart,
|
||||||
|
'bubble_chart': BubbleChart,
|
||||||
|
'line_chart': LineChartComponent,
|
||||||
|
'doughnut_chart': DoughnutChart,
|
||||||
|
'dynamic_chart': DynamicChart,
|
||||||
|
'financial_chart': FinancialChart,
|
||||||
|
'grid_view': GridViewComponent,
|
||||||
|
'scatter_chart': ScatterChartComponent,
|
||||||
|
'todo_chart': ToDoChartComponent
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentNameToIdentifier = {
|
||||||
|
'RadarChartComponent': 'radar_chart',
|
||||||
|
'PolarChartComponent': 'polar_chart',
|
||||||
|
'PieChartComponent': 'pie_chart',
|
||||||
|
'BarChart': 'bar_chart',
|
||||||
|
'BubbleChart': 'bubble_chart',
|
||||||
|
'LineChartComponent': 'line_chart',
|
||||||
|
'DoughnutChart': 'doughnut_chart',
|
||||||
|
'DynamicChart': 'dynamic_chart',
|
||||||
|
'FinancialChart': 'financial_chart',
|
||||||
|
'GridViewComponent': 'grid_view',
|
||||||
|
'ScatterChartComponent': 'scatter_chart',
|
||||||
|
'ToDoChartComponent': 'todo_chart'
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDashboardData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchDashboardData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await DashboardBuilderService.getById(id);
|
||||||
|
setDashboardlineId(response.dashbord1_Line[0].id);
|
||||||
|
setDashboardName(response.dashboard_name);
|
||||||
|
setDashboardLine(response.dashbord1_Line);
|
||||||
|
|
||||||
|
if (response.dashbord1_Line && response.dashbord1_Line[0]?.model) {
|
||||||
|
const modelData = response.dashbord1_Line[0].model;
|
||||||
|
const parsedModel = JSON.parse(modelData);
|
||||||
|
|
||||||
|
const transformedWidgets = Array.isArray(parsedModel.dashboard)
|
||||||
|
? parsedModel.dashboard.map((widget, index) => {
|
||||||
|
const widgetId = widget.i || `widget-${index}`;
|
||||||
|
const typeIdentifier = widget.type ||
|
||||||
|
(widget.component && typeof widget.component === 'string'
|
||||||
|
? componentNameToIdentifier[widget.component] || widget.component.toLowerCase().replace(' ', '_')
|
||||||
|
: 'unknown');
|
||||||
|
|
||||||
|
const componentRef = componentMap[typeIdentifier] || null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
i: widgetId,
|
||||||
|
x: widget.x || 0,
|
||||||
|
y: widget.y || 0,
|
||||||
|
w: widget.cols || 6,
|
||||||
|
h: widget.rows || 8,
|
||||||
|
type: typeIdentifier,
|
||||||
|
name: widget.name || availableWidgets.find(w => w.identifier === typeIdentifier)?.name || 'Unknown Widget',
|
||||||
|
component: componentRef,
|
||||||
|
chartTitle: widget.chartTitle,
|
||||||
|
showLegend: widget.showLegend,
|
||||||
|
showLabel: widget.showLabel,
|
||||||
|
xAxis: widget.xAxis,
|
||||||
|
yAxis: widget.yAxis
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
setDashboardWidgets(transformedWidgets);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching dashboard data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragStart = (e, widget) => {
|
||||||
|
e.dataTransfer.setData('widgetType', widget.identifier);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const widgetType = e.dataTransfer.getData('widgetType');
|
||||||
|
if (!widgetType) return;
|
||||||
|
|
||||||
|
const gridRect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const x = Math.floor((e.clientX - gridRect.left) / 100) * 2;
|
||||||
|
const y = Math.floor((e.clientY - gridRect.top) / 100) * 2;
|
||||||
|
|
||||||
|
const newWidgetId = `widget-${Date.now()}`;
|
||||||
|
const componentRef = componentMap[widgetType] || null;
|
||||||
|
const widgetName = availableWidgets.find(w => w.identifier === widgetType)?.name || 'Unknown Widget';
|
||||||
|
|
||||||
|
const newWidget = {
|
||||||
|
i: newWidgetId,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
w: 6,
|
||||||
|
h: 8,
|
||||||
|
type: widgetType,
|
||||||
|
name: widgetName,
|
||||||
|
component: componentRef,
|
||||||
|
chartTitle: widgetName,
|
||||||
|
showLegend: true,
|
||||||
|
showLabel: true,
|
||||||
|
xAxis: 'Month',
|
||||||
|
yAxis: 'Value'
|
||||||
|
};
|
||||||
|
|
||||||
|
setDashboardWidgets(prev => [...prev, newWidget]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLayoutChange = (layout) => {
|
||||||
|
const updatedWidgets = dashboardWidgets.map(widget => {
|
||||||
|
const updatedPosition = layout.find(item => item.i === widget.i);
|
||||||
|
if (updatedPosition) {
|
||||||
|
return { ...widget, x: updatedPosition.x, y: updatedPosition.y, w: updatedPosition.w, h: updatedPosition.h };
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
});
|
||||||
|
setDashboardWidgets(updatedWidgets);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeWidget = (widgetId) => {
|
||||||
|
setDashboardWidgets(prev => prev.filter(widget => widget.i !== widgetId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const editWidget = (widget) => {
|
||||||
|
setCurrentEditWidget(widget);
|
||||||
|
setEditModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveWidgetChanges = () => {
|
||||||
|
setDashboardWidgets(prev =>
|
||||||
|
prev.map(widget =>
|
||||||
|
widget.i === currentEditWidget.i ? { ...widget, ...currentEditWidget } : widget
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setEditModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
navigate('/dashboard/dashboard-new-all');
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveDashboard = async () => {
|
||||||
|
try {
|
||||||
|
if (!dashboardWidgets || dashboardWidgets.length === 0) {
|
||||||
|
console.warn("No widgets to save.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dashboardData = {
|
||||||
|
dashboard: dashboardWidgets.map(widget => ({
|
||||||
|
i: widget.i,
|
||||||
|
x: widget.x ?? 0,
|
||||||
|
y: widget.y ?? 0,
|
||||||
|
w: widget.w ?? 6,
|
||||||
|
h: widget.h ?? 8,
|
||||||
|
type: widget.type,
|
||||||
|
name: widget.name || "Unknown",
|
||||||
|
chartTitle: widget.chartTitle,
|
||||||
|
showLegend: widget.showLegend,
|
||||||
|
showLabel: widget.showLabel,
|
||||||
|
xAxis: widget.xAxis,
|
||||||
|
yAxis: widget.yAxis
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashbord1_Line = {
|
||||||
|
model: JSON.stringify(dashboardData)
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await DashboardBuilderService.updateLineData(dashboardlineId, dashbord1_Line);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
navigate('/dashboard/dashboard-new-all');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving dashboard:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
padding: 3,
|
||||||
|
backgroundColor: theme.palette.background.default
|
||||||
|
}}>
|
||||||
|
{/* Toolbar */}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: 3
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
>
|
||||||
|
{sidebarOpen ? 'Hide Components' : 'Add Components'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Typography variant="h5" color="text.primary">
|
||||||
|
{dashboardName}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
onClick={goBack}
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
onClick={saveDashboard}
|
||||||
|
>
|
||||||
|
Save Dashboard
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{/* Sidebar */}
|
||||||
|
{sidebarOpen && (
|
||||||
|
<Box sx={{
|
||||||
|
width: 250,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
padding: 2,
|
||||||
|
overflowY: 'auto',
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`,
|
||||||
|
boxShadow: theme.shadows[2]
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6" sx={{ marginBottom: 2 }}>
|
||||||
|
Available Components
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gap: 1
|
||||||
|
}}>
|
||||||
|
{availableWidgets.map(widget => (
|
||||||
|
<Card
|
||||||
|
key={widget.identifier}
|
||||||
|
sx={{
|
||||||
|
cursor: 'move',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.action.hover
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => onDragStart(e, widget)}
|
||||||
|
>
|
||||||
|
<CardContent sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '8px !important'
|
||||||
|
}}>
|
||||||
|
<DragIndicatorIcon sx={{ mr: 1, color: theme.palette.text.secondary }} />
|
||||||
|
<Typography variant="body2">{widget.name}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dashboard grid */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
padding: 2,
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: theme.palette.background.default
|
||||||
|
}}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
onDrop={onDrop}
|
||||||
|
>
|
||||||
|
<ResponsiveGridLayout
|
||||||
|
className="layout"
|
||||||
|
draggableCancel=".MuiButton-root, .MuiIconButton-root, button, [role='button']"
|
||||||
|
layouts={{
|
||||||
|
lg: dashboardWidgets.map(widget => ({
|
||||||
|
i: widget.i,
|
||||||
|
x: widget.x,
|
||||||
|
y: widget.y,
|
||||||
|
w: widget.w,
|
||||||
|
h: widget.h,
|
||||||
|
minW: 3,
|
||||||
|
minH: 3
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
||||||
|
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
|
||||||
|
rowHeight={30}
|
||||||
|
onLayoutChange={onLayoutChange}
|
||||||
|
isDraggable
|
||||||
|
isResizable
|
||||||
|
margin={[20, 20]}
|
||||||
|
>
|
||||||
|
{dashboardWidgets.map(widget => (
|
||||||
|
<Box key={widget.i} sx={{ overflow: 'hidden' }}>
|
||||||
|
<Card sx={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
boxShadow: theme.shadows[2]
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 1,
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
backgroundColor: theme.palette.background.paper
|
||||||
|
}}>
|
||||||
|
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||||||
|
{widget.name}
|
||||||
|
</Typography>
|
||||||
|
<Box>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
editWidget(widget);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
removeWidget(widget.i);
|
||||||
|
}}
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
>
|
||||||
|
<DeleteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<CardContent sx={{
|
||||||
|
flex: 1,
|
||||||
|
padding: 2,
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: 'calc(100% - 50px)'
|
||||||
|
}}>
|
||||||
|
{widget.component && (
|
||||||
|
<Box sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
<widget.component
|
||||||
|
chartTitle={widget.chartTitle || widget.name}
|
||||||
|
showLegend={widget.showLegend !== undefined ? widget.showLegend : true}
|
||||||
|
showLabel={widget.showLabel !== undefined ? widget.showLabel : true}
|
||||||
|
xAxis={widget.xAxis || 'Month'}
|
||||||
|
yAxis={widget.yAxis || 'Value'}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</ResponsiveGridLayout>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Edit widget dialog */}
|
||||||
|
<Dialog open={editModalOpen} onClose={() => setEditModalOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>Edit {currentEditWidget?.name}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
label="Chart Title"
|
||||||
|
fullWidth
|
||||||
|
value={currentEditWidget?.chartTitle || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCurrentEditWidget((prev) => ({ ...prev, chartTitle: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="X Axis Label"
|
||||||
|
fullWidth
|
||||||
|
value={currentEditWidget?.xAxis || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCurrentEditWidget((prev) => ({ ...prev, xAxis: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Y Axis Label"
|
||||||
|
fullWidth
|
||||||
|
value={currentEditWidget?.yAxis || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCurrentEditWidget((prev) => ({ ...prev, yAxis: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<FormControl>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={currentEditWidget?.showLegend ?? true}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCurrentEditWidget((prev) => ({ ...prev, showLegend: e.target.checked }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Show Legend"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={currentEditWidget?.showLabel ?? true}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCurrentEditWidget((prev) => ({ ...prev, showLabel: e.target.checked }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Show Labels"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setEditModalOpen(false)} startIcon={<CancelIcon />}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={saveWidgetChanges}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditNewDash;
|
||||||
162
src/components/dashboardnew/editdashboard/editformdashboard.js
Normal file
162
src/components/dashboardnew/editdashboard/editformdashboard.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Chip
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
Save as SaveIcon,
|
||||||
|
ArrowBack as ArrowBackIcon
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import DashboardBuilderService from "../../../../src/ApiServices/DashboardBuilderServices";
|
||||||
|
|
||||||
|
const EditFormNewDash = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const [header, setHeader] = useState({
|
||||||
|
dashboard_name: '',
|
||||||
|
secuirity_profile: '',
|
||||||
|
description: '',
|
||||||
|
add_to_home: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.state?.dashboard) {
|
||||||
|
setHeader(location.state.dashboard);
|
||||||
|
} else {
|
||||||
|
getById(id);
|
||||||
|
}
|
||||||
|
}, [id, location.state]);
|
||||||
|
|
||||||
|
const getById = (id) => {
|
||||||
|
DashboardBuilderService.getById(id)
|
||||||
|
.then((data) => {
|
||||||
|
setHeader(data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error fetching dashboard by ID:", err);
|
||||||
|
toast.error('Failed to fetch dashboard data.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value, type, checked } = e.target;
|
||||||
|
setHeader((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === 'checkbox' ? checked : value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
try {
|
||||||
|
const updatedDashboard = await DashboardBuilderService.updateDash(header);
|
||||||
|
if (updatedDashboard) {
|
||||||
|
toast.success('Dashboard updated successfully');
|
||||||
|
navigate('/dashboard/dashboard-new-all');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating dashboard:", error);
|
||||||
|
toast.error('Failed to update dashboard.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBack = () => {
|
||||||
|
navigate('/dashboard/dashboard-new-all');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card sx={{ p: 3 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||||
|
<Typography variant="h5">Dashboard</Typography>
|
||||||
|
<Chip
|
||||||
|
label="Edit Mode"
|
||||||
|
color="info"
|
||||||
|
size="small"
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box component="form" onSubmit={onSubmit}>
|
||||||
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 3, mb: 3 }}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
label="Dashboard Name"
|
||||||
|
name="dashboard_name"
|
||||||
|
value={header.dashboard_name || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
label="Security Profile"
|
||||||
|
name="secuirity_profile"
|
||||||
|
value={header.secuirity_profile || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth sx={{ gridColumn: '1 / -1' }}>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={header.description || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
name="add_to_home"
|
||||||
|
checked={header.add_to_home || false}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Add to Dashboard"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
onClick={onBack}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditFormNewDash;
|
||||||
39
src/components/dashboardnew/gadgets/barchart.js
Normal file
39
src/components/dashboardnew/gadgets/barchart.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Bar } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const BarChart = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Best Fruits',
|
||||||
|
data: [45, 37, 60, 70, 46, 33],
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Bar Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Bar data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarChart;
|
||||||
57
src/components/dashboardnew/gadgets/bubblechart.js
Normal file
57
src/components/dashboardnew/gadgets/bubblechart.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Bubble } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const BubbleChart = () => {
|
||||||
|
const data = {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ x: 10, y: 10, r: 10 },
|
||||||
|
{ x: 15, y: 5, r: 15 },
|
||||||
|
{ x: 26, y: 12, r: 23 },
|
||||||
|
{ x: 7, y: 8, r: 8 },
|
||||||
|
],
|
||||||
|
label: 'Investment Equities',
|
||||||
|
backgroundColor: 'rgba(255, 0, 0, 0.6)',
|
||||||
|
borderColor: 'blue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ x: 5, y: 15, r: 12 },
|
||||||
|
{ x: 20, y: 7, r: 8 },
|
||||||
|
{ x: 12, y: 18, r: 15 },
|
||||||
|
{ x: 8, y: 6, r: 10 },
|
||||||
|
],
|
||||||
|
label: 'Investment Bonds',
|
||||||
|
backgroundColor: 'rgba(0, 255, 0, 0.6)',
|
||||||
|
borderColor: 'green',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Bubble Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { min: 0, max: 30 },
|
||||||
|
y: { min: 0, max: 30 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Bubble data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BubbleChart;
|
||||||
28
src/components/dashboardnew/gadgets/chartutils.js
Normal file
28
src/components/dashboardnew/gadgets/chartutils.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// chartUtils.js
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
ArcElement,
|
||||||
|
RadialLinearScale,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from '../../../variables/chart';
|
||||||
|
|
||||||
|
// chartutils.js
|
||||||
|
import { Chart } from 'chart.js';
|
||||||
|
import { registerables } from 'chart.js';
|
||||||
|
|
||||||
|
export const registerChartJS = () => {
|
||||||
|
Chart.register(...registerables);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const commonChartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
};
|
||||||
49
src/components/dashboardnew/gadgets/doughnut.js
Normal file
49
src/components/dashboardnew/gadgets/doughnut.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Doughnut } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const DoughnutChart = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ["Download Sales", "In-Store Sales", "Mail-Order Sales"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [350, 450, 100],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.6)',
|
||||||
|
'rgba(54, 162, 235, 0.6)',
|
||||||
|
'rgba(255, 206, 86, 0.6)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Doughnut Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Doughnut data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DoughnutChart;
|
||||||
61
src/components/dashboardnew/gadgets/dynamicchart.js
Normal file
61
src/components/dashboardnew/gadgets/dynamicchart.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Button } from '@mui/material';
|
||||||
|
import { Bar } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const DynamicChart = () => {
|
||||||
|
const [chartType, setChartType] = useState('bar');
|
||||||
|
const labels = ['2006', '2007', '2008', '2009', '2010', '2011', '2012'];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [65, 59, 90, 81, 56, 55, 40],
|
||||||
|
label: 'Series A',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.6)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [28, 48, 40, 19, 96, 27, 100],
|
||||||
|
label: 'Series B',
|
||||||
|
backgroundColor: 'rgba(153, 102, 255, 0.6)'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Dynamic Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomize = () => {
|
||||||
|
setChartType(prev => prev === 'bar' ? 'line' : 'bar');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 500 }}>
|
||||||
|
<Bar
|
||||||
|
data={data}
|
||||||
|
options={options}
|
||||||
|
type={chartType}
|
||||||
|
/>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Button variant="contained" onClick={randomize}>
|
||||||
|
Toggle Chart Type
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicChart;
|
||||||
63
src/components/dashboardnew/gadgets/financialchart.js
Normal file
63
src/components/dashboardnew/gadgets/financialchart.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Line } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const FinancialChart = ({ data }) => {
|
||||||
|
const defaultData = [
|
||||||
|
{ date: '2024-03-01', price: 120 },
|
||||||
|
{ date: '2024-03-02', price: 125 },
|
||||||
|
{ date: '2024-03-03', price: 130 },
|
||||||
|
{ date: '2024-03-04', price: 128 },
|
||||||
|
{ date: '2024-03-05', price: 135 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const stockData = data?.length > 0 ? data : defaultData;
|
||||||
|
|
||||||
|
const chartData = {
|
||||||
|
labels: stockData.map(item => item.date),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Stock Price',
|
||||||
|
data: stockData.map(item => item.price),
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
fill: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Financial Data Chart',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem) => `$${tooltipItem.raw.toFixed(2)}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: { display: true, text: 'Date' },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: { display: true, text: 'Price (USD)' },
|
||||||
|
ticks: { callback: (value) => `$${value.toFixed(2)}` },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Line data={chartData} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinancialChart;
|
||||||
107
src/components/dashboardnew/gadgets/gridview.js
Normal file
107
src/components/dashboardnew/gadgets/gridview.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
CircularProgress,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
const GridViewComponent = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [selectedRow, setSelectedRow] = useState(null);
|
||||||
|
|
||||||
|
// Simulated data - replace with actual API call
|
||||||
|
const mockData = [
|
||||||
|
{ usrGrp: 1, groupName: 'Admin', groupDesc: 'Administrators', groupLevel: 1, status: 'Active', updateDateFormated: '2023-01-01' },
|
||||||
|
{ usrGrp: 2, groupName: 'Users', groupDesc: 'Regular users', groupLevel: 2, status: 'Active', updateDateFormated: '2023-01-02' },
|
||||||
|
{ usrGrp: 3, groupName: 'Guests', groupDesc: 'Guest access', groupLevel: 3, status: 'Inactive', updateDateFormated: '2023-01-03' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Simulate data loading
|
||||||
|
React.useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setData(mockData);
|
||||||
|
setLoading(false);
|
||||||
|
}, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePageChange = (event, value) => {
|
||||||
|
setPage(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (row) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
User Group Maintenance
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : error ? (
|
||||||
|
<Typography color="error">{error}</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>User Group No</TableCell>
|
||||||
|
<TableCell>Group Name</TableCell>
|
||||||
|
<TableCell>Description</TableCell>
|
||||||
|
<TableCell>Group Level</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell>Updated Date</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.usrGrp}
|
||||||
|
hover
|
||||||
|
selected={selectedRow?.usrGrp === row.usrGrp}
|
||||||
|
onClick={() => handleRowClick(row)}
|
||||||
|
>
|
||||||
|
<TableCell>{row.usrGrp}</TableCell>
|
||||||
|
<TableCell>{row.groupName}</TableCell>
|
||||||
|
<TableCell>{row.groupDesc}</TableCell>
|
||||||
|
<TableCell>{row.groupLevel}</TableCell>
|
||||||
|
<TableCell>{row.status}</TableCell>
|
||||||
|
<TableCell>{row.updateDateFormated}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Pagination
|
||||||
|
count={3}
|
||||||
|
page={page}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GridViewComponent;
|
||||||
69
src/components/dashboardnew/gadgets/linechart.js
Normal file
69
src/components/dashboardnew/gadgets/linechart.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Button } from '@mui/material';
|
||||||
|
import { Line } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const LineChartComponent = ({
|
||||||
|
chartTitle = 'Line Chart',
|
||||||
|
showLegend = true,
|
||||||
|
showLabel = true,
|
||||||
|
xAxis = 'Category',
|
||||||
|
yAxis = 'Value'
|
||||||
|
}) => {
|
||||||
|
const [data, setData] = useState({
|
||||||
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Series A',
|
||||||
|
data: [65, 59, 80, 81, 56, 55, 40],
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Series B',
|
||||||
|
data: [28, 48, 40, 19, 86, 27, 90],
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: showLegend },
|
||||||
|
title: { display: true, text: chartTitle },
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { title: { display: showLabel, text: xAxis } },
|
||||||
|
y: { title: { display: showLabel, text: yAxis } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomize = () => {
|
||||||
|
setData(prev => ({
|
||||||
|
...prev,
|
||||||
|
datasets: prev.datasets.map(dataset => ({
|
||||||
|
...dataset,
|
||||||
|
data: Array.from({ length: 7 }, () => Math.floor(Math.random() * 100) + 1)
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Line data={data} options={options} />
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Button variant="outlined" onClick={randomize}>
|
||||||
|
Randomize Data
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineChartComponent;
|
||||||
37
src/components/dashboardnew/gadgets/piechart.js
Normal file
37
src/components/dashboardnew/gadgets/piechart.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Pie } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const PieChartComponent = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['SciFi', 'Drama', 'Comedy'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [30, 50, 20],
|
||||||
|
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Pie Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Pie data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PieChartComponent;
|
||||||
43
src/components/dashboardnew/gadgets/polarchart.js
Normal file
43
src/components/dashboardnew/gadgets/polarchart.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { PolarArea } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const PolarChartComponent = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['Download Sales', 'In-Store Sales', 'Mail Sales', 'Telesales', 'Corporate Sales'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [300, 500, 100, 40, 120],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.6)',
|
||||||
|
'rgba(54, 162, 235, 0.6)',
|
||||||
|
'rgba(255, 206, 86, 0.6)',
|
||||||
|
'rgba(75, 192, 192, 0.6)',
|
||||||
|
'rgba(153, 102, 255, 0.6)',
|
||||||
|
],
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Polar Area Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<PolarArea data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PolarChartComponent;
|
||||||
44
src/components/dashboardnew/gadgets/radarchart.js
Normal file
44
src/components/dashboardnew/gadgets/radarchart.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Radar } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const RadarChartComponent = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Series A',
|
||||||
|
data: [65, 59, 90, 81, 56, 55, 40],
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Series B',
|
||||||
|
data: [28, 48, 40, 19, 96, 27, 100],
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Radar Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Radar data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RadarChartComponent;
|
||||||
45
src/components/dashboardnew/gadgets/scatterchart.js
Normal file
45
src/components/dashboardnew/gadgets/scatterchart.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { Scatter } from 'react-chartjs-2';
|
||||||
|
import { registerChartJS, commonChartOptions } from './chartutils';
|
||||||
|
|
||||||
|
registerChartJS();
|
||||||
|
|
||||||
|
const ScatterChartComponent = () => {
|
||||||
|
const data = {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Scatter Dataset',
|
||||||
|
data: [
|
||||||
|
{ x: 1, y: 1 },
|
||||||
|
{ x: 2, y: 3 },
|
||||||
|
{ x: 3, y: -2 },
|
||||||
|
{ x: 4, y: 4 },
|
||||||
|
{ x: 5, y: -3 },
|
||||||
|
],
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...commonChartOptions,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Scatter Chart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { type: 'linear', position: 'bottom' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: 400 }}>
|
||||||
|
<Scatter data={data} options={options} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScatterChartComponent;
|
||||||
93
src/components/dashboardnew/gadgets/todochart.js
Normal file
93
src/components/dashboardnew/gadgets/todochart.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const ToDoChartComponent = () => {
|
||||||
|
const [todo, setTodo] = useState('');
|
||||||
|
const [todoList, setTodoList] = useState(['todo 1']);
|
||||||
|
|
||||||
|
const addTodo = (newTodo) => {
|
||||||
|
if (newTodo.trim()) {
|
||||||
|
setTodoList([...todoList, newTodo]);
|
||||||
|
setTodo('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTodo = (index) => {
|
||||||
|
// Don't allow removing the first item ('todo 1')
|
||||||
|
if (index === 0) return;
|
||||||
|
|
||||||
|
const updatedTodoList = todoList.filter((_, i) => i !== index);
|
||||||
|
setTodoList(updatedTodoList);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '15px' }}>
|
||||||
|
<table className="table" style={{ width: '100%' }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: '10%' }}>#</th>
|
||||||
|
<th style={{ width: '70%' }}>Item</th>
|
||||||
|
<th style={{ width: '20%', textAlign: 'right' }}>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{todoList.map((todoItem, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{index + 1}</td>
|
||||||
|
<td>{todoItem}</td>
|
||||||
|
<td style={{ textAlign: 'right' }}>
|
||||||
|
{index !== 0 && ( // Don't show delete button for the first item
|
||||||
|
<button
|
||||||
|
onClick={() => removeTodo(index)}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#ff4444'
|
||||||
|
}}
|
||||||
|
title="Remove item"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
value={todo}
|
||||||
|
onChange={(e) => setTodo(e.target.value)}
|
||||||
|
placeholder="Add new todo"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td style={{ textAlign: 'right' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => addTodo(todo)}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#4CAF50',
|
||||||
|
fontSize: '1.2em'
|
||||||
|
}}
|
||||||
|
title="Add item"
|
||||||
|
disabled={!todo.trim()}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToDoChartComponent;
|
||||||
61
src/context/SystemParameterContext.js
Normal file
61
src/context/SystemParameterContext.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
import { getSysParameter } from '../ApiServices/SytemparameterApi';
|
||||||
|
|
||||||
|
const SystemParameterContext = createContext();
|
||||||
|
|
||||||
|
export const useSystemParameters = () => {
|
||||||
|
const context = useContext(SystemParameterContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useSystemParameters must be used within a SystemParameterProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SystemParameterProvider = ({ children }) => {
|
||||||
|
const [systemParameters, setSystemParameters] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSystemParameters = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getSysParameter(1); // Assuming 1 is the default ID for system parameters
|
||||||
|
setSystemParameters(data);
|
||||||
|
console.log("system parameters data ", data);
|
||||||
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to fetch system parameters');
|
||||||
|
console.error('Error fetching system parameters:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSystemParameters();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
systemParameters,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refetch: async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getSysParameter(1);
|
||||||
|
setSystemParameters(data);
|
||||||
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to fetch system parameters');
|
||||||
|
console.error('Error fetching system parameters:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SystemParameterContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</SystemParameterContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,5 +17,3 @@ code {
|
|||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
src/index.js
16
src/index.js
@@ -1,22 +1,12 @@
|
|||||||
// index.js
|
// index.js
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
|
|
||||||
const theme = createTheme({
|
import { createRoot } from 'react-dom/client';
|
||||||
// your theme configuration
|
|
||||||
});
|
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
const container = document.getElementById('root');
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
|
root.render(<App />);
|
||||||
root.render(
|
|
||||||
<ErrorBoundary>
|
|
||||||
<ThemeProvider theme={theme}>
|
|
||||||
<App />
|
|
||||||
</ThemeProvider>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
const TOKEN_KEY = 'authToken';
|
const TOKEN_KEY = 'CurrentUser';
|
||||||
|
|
||||||
|
|
||||||
export const setToken = (token) => {
|
export const setCurrentUser = (user) => {
|
||||||
localStorage.setItem(TOKEN_KEY, token);
|
localStorage.setItem(TOKEN_KEY, user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCurrentUser = () => {
|
||||||
|
return JSON.parse(localStorage.getItem(TOKEN_KEY));
|
||||||
|
};
|
||||||
|
|
||||||
export const getToken = () => {
|
export const getToken = () => {
|
||||||
return localStorage.getItem(TOKEN_KEY);
|
const userData = localStorage.getItem(TOKEN_KEY);
|
||||||
|
return userData ? JSON.parse(userData).token : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const removeToken = () => {
|
export const removeCurrentUser = () => {
|
||||||
localStorage.removeItem(TOKEN_KEY);
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,3 +25,7 @@ export const removeToken = () => {
|
|||||||
export const isTokenAvailable = () => {
|
export const isTokenAvailable = () => {
|
||||||
return !!localStorage.getItem(TOKEN_KEY); // Returns true if token exists
|
return !!localStorage.getItem(TOKEN_KEY); // Returns true if token exists
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
233
src/variables/chart.js
Normal file
233
src/variables/chart.js
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/*!
|
||||||
|
=========================================================
|
||||||
|
* Argon Dashboard React - v1.2.4
|
||||||
|
=========================================================
|
||||||
|
* Product Page: https://www.creative-tim.com/product/argon-dashboard-react
|
||||||
|
* Copyright 2024 Creative Tim (https://www.creative-tim.com)
|
||||||
|
* Licensed under MIT (https://github.com/creativetimofficial/argon-dashboard-react/blob/master/LICENSE.md)
|
||||||
|
* Coded by Creative Tim
|
||||||
|
=========================================================
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// Chart.js v3+ compatible version - UPDATED
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const colors = {
|
||||||
|
gray: {
|
||||||
|
100: "#f6f9fc",
|
||||||
|
200: "#e9ecef",
|
||||||
|
300: "#dee2e6",
|
||||||
|
400: "#ced4da",
|
||||||
|
500: "#adb5bd",
|
||||||
|
600: "#8898aa",
|
||||||
|
700: "#525f7f",
|
||||||
|
800: "#32325d",
|
||||||
|
900: "#212529",
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
default: "#172b4d",
|
||||||
|
primary: "#5e72e4",
|
||||||
|
secondary: "#f4f5f7",
|
||||||
|
info: "#11cdef",
|
||||||
|
success: "#2dce89",
|
||||||
|
danger: "#f5365c",
|
||||||
|
warning: "#fb6340",
|
||||||
|
},
|
||||||
|
black: "#12263F",
|
||||||
|
white: "#FFFFFF",
|
||||||
|
transparent: "transparent",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
// Chart.js global options
|
||||||
|
function chartOptions() {
|
||||||
|
const mode = "light";
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: "index",
|
||||||
|
intersect: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
// New scale configuration format for Chart.js v3+
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
borderDash: [2],
|
||||||
|
borderDashOffset: [2],
|
||||||
|
color: mode === "dark" ? colors.gray[900] : colors.gray[300],
|
||||||
|
drawBorder: false,
|
||||||
|
drawTicks: false,
|
||||||
|
zeroLineColor: mode === "dark" ? colors.gray[900] : colors.gray[300],
|
||||||
|
zeroLineBorderDash: [2],
|
||||||
|
zeroLineBorderDashOffset: [2],
|
||||||
|
},
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
padding: 10,
|
||||||
|
callback: function (value) {
|
||||||
|
if (!(value % 10)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
drawOnChartArea: false,
|
||||||
|
drawTicks: false,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
padding: 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse global options
|
||||||
|
function parseOptions(Chart, options) {
|
||||||
|
// Update defaults for Chart.js v3+
|
||||||
|
Chart.defaults.color = colors.gray[600];
|
||||||
|
Chart.defaults.font.family = 'Open Sans';
|
||||||
|
Chart.defaults.font.size = 13;
|
||||||
|
|
||||||
|
// Apply any additional options
|
||||||
|
if (options) {
|
||||||
|
for (var item in options) {
|
||||||
|
if (typeof options[item] !== "object") {
|
||||||
|
Chart.defaults[item] = options[item];
|
||||||
|
} else {
|
||||||
|
parseOptions(Chart.defaults[item], options[item]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 1 of Chart inside src/views/Index.js (Sales value - Card)
|
||||||
|
const chartExample1 = {
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
color: colors.gray[900],
|
||||||
|
zeroLineColor: colors.gray[900],
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
callback: function (value) {
|
||||||
|
if (!(value % 10)) {
|
||||||
|
return "$" + value + "k";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
var label = context.dataset.label || "";
|
||||||
|
var yLabel = context.parsed.y;
|
||||||
|
var content = "";
|
||||||
|
|
||||||
|
if (context.chart.data.datasets.length > 1) {
|
||||||
|
content += label;
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "$" + yLabel + "k";
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data1: {
|
||||||
|
labels: ["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Performance",
|
||||||
|
data: [0, 20, 10, 30, 15, 40, 20, 60, 60],
|
||||||
|
borderColor: colors.theme.primary,
|
||||||
|
backgroundColor: "rgba(94, 114, 228, 0.1)",
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
data2: {
|
||||||
|
labels: ["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Performance",
|
||||||
|
data: [0, 20, 5, 25, 10, 30, 15, 40, 40],
|
||||||
|
borderColor: colors.theme.primary,
|
||||||
|
backgroundColor: "rgba(94, 114, 228, 0.1)",
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 2 of Chart inside src/views/Index.js (Total orders - Card)
|
||||||
|
const chartExample2 = {
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
ticks: {
|
||||||
|
callback: function (value) {
|
||||||
|
if (!(value % 10)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
var label = context.dataset.label || "";
|
||||||
|
var yLabel = context.parsed.y;
|
||||||
|
var content = "";
|
||||||
|
if (context.chart.data.datasets.length > 1) {
|
||||||
|
content += label;
|
||||||
|
}
|
||||||
|
content += yLabel;
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
labels: ["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Sales",
|
||||||
|
data: [25, 20, 30, 22, 17, 29],
|
||||||
|
backgroundColor: colors.theme.warning,
|
||||||
|
borderRadius: 6,
|
||||||
|
maxBarThickness: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
chartOptions, // used inside src/views/Index.js
|
||||||
|
parseOptions, // used inside src/views/Index.js
|
||||||
|
chartExample1, // used inside src/views/Index.js
|
||||||
|
chartExample2, // used inside src/views/Index.js
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user