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",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@mui/styles": "^5.16.4",
|
||||
@@ -20,19 +17,36 @@
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"ajv": "^6.12.6",
|
||||
"ajv-keywords": "^3.5.2",
|
||||
"axios": "^1.6.7",
|
||||
"lucide-react": "^0.511.0",
|
||||
"chart.js": "^4.4.9",
|
||||
"mdb-react-ui-kit": "^7.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-barcode": "^1.5.3",
|
||||
"react-data-grid": "^7.0.0-beta.44",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-data-grid": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-grid-layout": "^1.5.1",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-qr-code": "^2.0.14",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"react-scripts": "^5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-toastify": "^11.0.5",
|
||||
"schema-utils": "^3.3.0",
|
||||
"web-vitals": "^2.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
@@ -25,7 +25,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>CloudnSure</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
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"] {
|
||||
margin-right: 5px;
|
||||
} */
|
||||
|
||||
|
||||
123
src/App.js
123
src/App.js
@@ -1,37 +1,114 @@
|
||||
import React from "react";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { SystemParameterProvider } from './context/SystemParameterContext';
|
||||
import {
|
||||
BrowserRouter,
|
||||
Routes,
|
||||
Route,
|
||||
Navigate
|
||||
} from "react-router-dom";
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
|
||||
// Import components
|
||||
import Login from "./components/Login/Login";
|
||||
import Dashboard from "./components/Dashboard/dashboard";
|
||||
import UserMaintance from "./components/Dashboard/UserMaintance";
|
||||
import UserGroupMaintance from "./components/Dashboard/UserGroupMaintance/UserGroupMaintance";
|
||||
import HomePage from "./components/Dashboard/HomePage";
|
||||
import Setup from "./components/Dashboard/Setup";
|
||||
import UserMaintenance from "./components/Dashboard/UserMaintance";
|
||||
import UserGroupMaintenance from "./components/Dashboard/UserGroupMaintance/UserGroupMaintance";
|
||||
import MenuMaintenance from "./components/Dashboard/MenuMaintance/MenuMaintance";
|
||||
import MenuAccessControl from "./components/Dashboard/MenuAccessControl/MenuAccessControl";
|
||||
import SystemParameters from "./components/Dashboard/SystemParameters/SystemParameters";
|
||||
import AccessType from "./components/Dashboard/AccessType/AccessType";
|
||||
import ApiRegistry from "./components/Dashboard/ApiRegistery/ApiRegistery";
|
||||
import TokenRegistry from "./components/Dashboard/TokenRegistery/TokenRegistery";
|
||||
import CodeExtension from "./components/Dashboard/Codeextension";
|
||||
import Extension from "./components/Dashboard/Extension";
|
||||
import DynamicTable from "./components/Dashboard/Dynamictable";
|
||||
import Form from "./components/Dashboard/Form";
|
||||
import ForgotPassword from "./components/Login/ForgotPassword";
|
||||
import CreateAccount from "./components/Login/CreateAccount";
|
||||
import DashboardBuilder from "./components/Dashboard/DashboardBuilder";
|
||||
import Report from "./components/Dashboard/reports/Report";
|
||||
import SequenceGenerator from "./components/Dashboard/document sequence/sequencegenerator";
|
||||
import About from "./components/Dashboard/dropdown/about";
|
||||
import Profile from "./components/Dashboard/dropdown/profile";
|
||||
import DashboardRunner from "./components/dashboardnew/dashboardrunner/dashboardrunner";
|
||||
import DashboardRunnerAll from "./components/dashboardnew/dashboardrunner/dashboardrunnerall";
|
||||
import DashboardNewAll from "./components/dashboardnew/dashboardbuildernewall";
|
||||
import DashboardNewAdd from "./components/dashboardnew/dashboardadd/dashboardbuilderadd";
|
||||
import DashboardNewEdit from "./components/dashboardnew/editdashboard/editformdashboard";
|
||||
import EditNewDash from "./components/dashboardnew/editdashboard/editdashboard";
|
||||
import SubMenuMaintenance from "./components/Dashboard/sub menu/submenumaintanence";
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#1976d2',
|
||||
},
|
||||
secondary: {
|
||||
main: '#dc004e',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<Router>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SystemParameterProvider>
|
||||
<BrowserRouter>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={1500}
|
||||
hideProgressBar={false}
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Login />} />
|
||||
<Route path="/Dashboard" element={<Dashboard />} />
|
||||
<Route path="/UserGroupMaintance" element={<UserGroupMaintance />} />
|
||||
<Route path="/Dashboard/UserMaintance" element={<UserMaintance />} />
|
||||
<Route path="/CodeExtension" element={<CodeExtension />} />
|
||||
<Route path="/Dashboard/DashboardBuilder" element={<DashboardBuilder />} />
|
||||
<Route path="/Extension" element={<Extension />} />
|
||||
<Route path="/Dynamictable" element={<DynamicTable />} />
|
||||
<Route path="/Form" element={<Form />} />
|
||||
<Route path="/ForgotPassword" element={<ForgotPassword />} />
|
||||
<Route path="/CreateAccount" element={<CreateAccount />} />
|
||||
|
||||
{/* Main dashboard route */}
|
||||
<Route path="/dashboard" element={<Dashboard />}>
|
||||
<Route index element={<HomePage />} />
|
||||
|
||||
{/* Setup section with all maintenance routes */}
|
||||
<Route path="setup" element={<Setup />}>
|
||||
<Route index element={<div>Select a setup option from the menu</div>} />
|
||||
<Route path="user-maintenance" element={<UserMaintenance />} />
|
||||
<Route path="user-group-maintenance" element={<UserGroupMaintenance />} />
|
||||
<Route path="menu-maintenance" element={<MenuMaintenance />} />
|
||||
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
|
||||
|
||||
<Route path="menu-access-control" element={<MenuAccessControl />} />
|
||||
<Route path="system-parameters" element={<SystemParameters />} />
|
||||
<Route path="access-type" element={<AccessType />} />
|
||||
<Route path="api-registry" element={<ApiRegistry />} />
|
||||
<Route path="token-registry" element={<TokenRegistry />} />
|
||||
<Route path="document-sequence" element={<SequenceGenerator />} />
|
||||
{/* Additional components */}
|
||||
<Route path="code-extension" element={<CodeExtension />} />
|
||||
<Route path="dynamic-table" element={<DynamicTable />} />
|
||||
|
||||
</Route>
|
||||
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
|
||||
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
|
||||
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
|
||||
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
|
||||
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
|
||||
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
|
||||
|
||||
<Route path="reports" element={<Report />} />
|
||||
<Route path="about" element={<About />} />
|
||||
<Route path="profile" element={<Profile />} />
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</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";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
||||
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
||||
'&:nth-of-type(odd)': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action.selected,
|
||||
},
|
||||
}));
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleModal }) {
|
||||
const handleGoToPage1 = () => {
|
||||
if (apiRef.current) {
|
||||
apiRef.current.setPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer className="flex justify-between p-2 bg-gray-200">
|
||||
<Button
|
||||
onClick={handleGoToPage1}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600"
|
||||
>
|
||||
Go to page 1
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleModal}
|
||||
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600"
|
||||
>
|
||||
Add item
|
||||
</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiRegistery() {
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
||||
// eslint-disable-next-line
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const apiRef = useRef(null);
|
||||
const APIRegistry = () => {
|
||||
const [apiEntries, setApiEntries] = useState([]);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [currentApiEntry, setCurrentApiEntry] = useState({
|
||||
table_name: "",
|
||||
isActive: false,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||
const [visibleColumns, setVisibleColumns] = useState({
|
||||
id: true,
|
||||
table_name: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
isActive: true,
|
||||
actions: true,
|
||||
});
|
||||
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
|
||||
const [recordsAnchorEl, setRecordsAnchorEl] = useState(null);
|
||||
const columnsMenuOpen = Boolean(columnsAnchorEl);
|
||||
const recordsMenuOpen = Boolean(recordsAnchorEl);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("token"); // Get token from local storage
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${api}/Api_registery_header/Api_registery_header`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
setMenuItems(data);
|
||||
setLoading(true);
|
||||
const data = await fetchRegistry();
|
||||
setApiEntries(data || []);
|
||||
} 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) => {
|
||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
||||
const handleColumnsMenuClick = (event) => {
|
||||
setColumnsAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
width: 300,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "table_name",
|
||||
headerName: "Table Name",
|
||||
width: 350,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 150,
|
||||
renderCell: ({ row }) => (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleThreeDotsClick(row.id)}
|
||||
>
|
||||
<BsThreeDotsVertical /> {/* Using react-icons */}
|
||||
</div>
|
||||
{selectedMenuItem === row.id && (
|
||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Edit
|
||||
</button>
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
const handleRecordsMenuClick = (event) => {
|
||||
setRecordsAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setColumnsAnchorEl(null);
|
||||
setRecordsAnchorEl(null);
|
||||
};
|
||||
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns((prev) => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value, type, checked } = event.target;
|
||||
setCurrentApiEntry((prev) => ({
|
||||
...prev,
|
||||
[name]: type === 'checkbox' ? checked : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSearch = (query) => {
|
||||
setSearchQuery(query);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
setLoading(true);
|
||||
let response;
|
||||
|
||||
if (isEditing) {
|
||||
response = await updateRegistry(currentApiEntry.id, currentApiEntry);
|
||||
toast.success("API entry updated successfully!");
|
||||
} else {
|
||||
response = await addRegistry(currentApiEntry);
|
||||
toast.success("API entry added successfully!");
|
||||
}
|
||||
|
||||
// Refresh the data
|
||||
const data = await fetchRegistry();
|
||||
setApiEntries(data || []);
|
||||
setOpenModal(false);
|
||||
} catch (error) {
|
||||
toast.error(error.message || "Failed to save API entry");
|
||||
console.error("Save error:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenModal = (entry = {
|
||||
table_name: "",
|
||||
isActive: false,
|
||||
}) => {
|
||||
setIsEditing(!!entry.id);
|
||||
setCurrentApiEntry(entry);
|
||||
setOpenModal(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setOpenModal(false);
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await deleteRegistry(id);
|
||||
const data = await fetchRegistry();
|
||||
setApiEntries(data || []);
|
||||
toast.success("API entry deleted successfully!");
|
||||
} catch (error) {
|
||||
toast.error(error.message || "Failed to delete API entry");
|
||||
console.error("Delete error:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecordsPerPageChange = (value) => {
|
||||
setRecordsPerPage(value);
|
||||
setCurrentPage(1);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
// Filter and pagination logic
|
||||
const filteredAPIEntries = apiEntries.filter(item =>
|
||||
item.table_name?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(filteredAPIEntries.length / recordsPerPage);
|
||||
const slicedAPIEntries = filteredAPIEntries.slice(
|
||||
(currentPage - 1) * recordsPerPage,
|
||||
currentPage * recordsPerPage
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center mt-5">
|
||||
<Box className="w-full max-w-7xl">
|
||||
<div className="bg-white p-4 rounded shadow-md">
|
||||
<h1 className="text-2xl font-bold mb-4 text-white bg-gray-400 p-3">
|
||||
API Registry
|
||||
</h1>
|
||||
<DataGrid
|
||||
rows={menuItems}
|
||||
columns={columns}
|
||||
components={{
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleModal={() => setIsModalOpen(true)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Box display="flex" justifyContent="center" alignItems="center" height="80vh">
|
||||
<CircularProgress />
|
||||
</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 {
|
||||
Box,
|
||||
Button,
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
Autocomplete,
|
||||
} from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { FaEllipsisV } from "react-icons/fa"; // Importing react-icons instead of Font Awesome
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
|
||||
import { Link } from "react-router-dom";
|
||||
import Extension from "./Extension";
|
||||
@@ -44,7 +45,7 @@ function CodeExtension() {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
||||
const token = localStorage.getItem("token"); // Get token from local storage
|
||||
try {
|
||||
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/extension`, {
|
||||
headers: {
|
||||
@@ -104,7 +105,7 @@ function CodeExtension() {
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleThreeDotsClick(row.id)}
|
||||
>
|
||||
<FaEllipsisV /> {/* Using react-icons instead of Font Awesome */}
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</div>
|
||||
{selectedMenuItem === row.id && (
|
||||
<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]);
|
||||
|
||||
const fetchForms = async () => {
|
||||
const token = localStorage.getItem("authToken"); // Get token from local storage
|
||||
const token = localStorage.getItem("token"); // Get token from local storage
|
||||
try {
|
||||
const response = await axios.get(`${api}/api/form_setup`, {
|
||||
headers: {
|
||||
@@ -55,7 +55,7 @@ const DynamicTable = () => {
|
||||
};
|
||||
|
||||
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 {
|
||||
await axios.delete(`${api}/api/form_setup/${id}`, {
|
||||
headers: {
|
||||
@@ -69,7 +69,7 @@ const DynamicTable = () => {
|
||||
};
|
||||
|
||||
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 {
|
||||
await axios.post(
|
||||
`${api}/api/dynamic_form_build`,
|
||||
|
||||
@@ -23,7 +23,7 @@ const Extension = ({ onSubmit }) => {
|
||||
onSubmit(formData.dataType);
|
||||
}
|
||||
// Navigate to CodeExtension page with the form data
|
||||
navigate('/CodeExtension', { state: { formData } });
|
||||
navigate('/Codeextension', { state: { formData } });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
function Form() {
|
||||
function DynamicForm() {
|
||||
const [components, setComponents] = useState([
|
||||
{
|
||||
id: uuidv4(),
|
||||
@@ -214,4 +214,4 @@ function Form() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Form;
|
||||
export default DynamicForm;
|
||||
|
||||
@@ -1,110 +1,34 @@
|
||||
// HomePage.js
|
||||
|
||||
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 }) => (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-all duration-200">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className={`p-3 rounded-lg ${color}`}>
|
||||
<Icon className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-gray-500 text-sm font-medium">{title}</h3>
|
||||
<p className="text-2xl font-bold text-gray-800">{value}</p>
|
||||
</div>
|
||||
</div>
|
||||
const Card = ({ index }) => {
|
||||
return (
|
||||
<div className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
|
||||
<h3 className="text-lg font-semibold mb-2">INDEX {index}</h3>
|
||||
<p className="text-gray-600">{index}.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-3xl font-bold text-gray-800">Welcome Back!</h1>
|
||||
<div className="text-sm text-gray-500">Last updated: {new Date().toLocaleDateString()}</div>
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4">
|
||||
<h2 className="text-3xl font-semibold text-gray-700 mb-6">Welcome to the Dashboard!</h2>
|
||||
<div className="flex flex-wrap justify-center">
|
||||
<Card index={1} />
|
||||
<Card index={2} />
|
||||
<Card index={3} />
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
icon={FaUsers}
|
||||
title="Total Users"
|
||||
value="1,234"
|
||||
color="bg-gradient-to-r from-purple-500 to-indigo-500"
|
||||
<div className="w-full mt-8 flex justify-center">
|
||||
{/* Add BarChart component */}
|
||||
<BarChart
|
||||
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
||||
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
|
||||
width={700}
|
||||
height={400}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,177 +1,642 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
||||
import "./MenuMaintance.css";
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
|
||||
const handleGoToPage1 = () => {
|
||||
if (apiRef.current) {
|
||||
apiRef.current.setPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer>
|
||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
||||
<Button onClick={handleModal}>+</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Checkbox,
|
||||
Pagination,
|
||||
Select,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Modal,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
CircularProgress,
|
||||
Chip,
|
||||
Grid,
|
||||
InputAdornment
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Edit as EditIcon,
|
||||
Delete as DeleteIcon,
|
||||
Add as AddIcon,
|
||||
Menu as MenuIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
Close as CloseIcon,
|
||||
Download as DownloadIcon,
|
||||
Upload as UploadIcon,
|
||||
InsertDriveFile as ExcelIcon,
|
||||
Search as SearchIcon,
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from "@mui/icons-material";
|
||||
import { toast } from "react-toastify";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as XLSX from "xlsx";
|
||||
import {
|
||||
fetchMenuItems,
|
||||
addMenuItem,
|
||||
updateMenuItem,
|
||||
deleteMenuItem,
|
||||
getSubmenuItems,
|
||||
addSubmenuItem,
|
||||
updateSubmenuItem,
|
||||
deleteSubmenuItem
|
||||
} from "../../../ApiServices/menumaintananceapi";
|
||||
|
||||
function MenuMaintenance() {
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const apiRef = useRef(null);
|
||||
const [subMenuItems, setSubMenuItems] = useState([]);
|
||||
const [showAddEditPopup, setShowAddEditPopup] = useState(false);
|
||||
const [currentMenuItem, setCurrentMenuItem] = useState({
|
||||
menuItemDesc: "",
|
||||
menuId: 0,
|
||||
itemSeq: "",
|
||||
moduleName: "",
|
||||
main_menu_action_name: "",
|
||||
main_menu_icon_name: "",
|
||||
status: "true"
|
||||
});
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||
const [visibleColumns, setVisibleColumns] = useState({
|
||||
menuItemDesc: true,
|
||||
menu_id: true,
|
||||
itemSeq: true,
|
||||
moduleName: true,
|
||||
main_menu_action_name: true,
|
||||
main_menu_icon_name: true,
|
||||
status: true,
|
||||
});
|
||||
const [isSubMenu, setIsSubMenu] = useState(false);
|
||||
const [parentMenuItemId, setParentMenuItemId] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [pageAnchorEl, setPageAnchorEl] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 3000);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
try {
|
||||
const response = await fetch(`${api}/api1/submenu1`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
// Flatten the nested subMenus array
|
||||
const flattenedData = data.flatMap((menuItem) => [
|
||||
menuItem,
|
||||
...menuItem.subMenus,
|
||||
]);
|
||||
// Set unique IDs for each menu item
|
||||
const menuItemsWithIds = flattenedData.map((menuItem, index) => ({
|
||||
...menuItem,
|
||||
id: index + 1,
|
||||
}));
|
||||
setMenuItems(menuItemsWithIds);
|
||||
console.log("Fetching Menu Items...");
|
||||
const data = await fetchMenuItems();
|
||||
setMenuItems(data);
|
||||
console.log("Fetched Menu Items:", data);
|
||||
toast.success("Menu items fetched successfully!");
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
console.error("Fetching error:", error);
|
||||
setError(error.message);
|
||||
toast.error("Error fetching menu items.");
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleThreeDotsClick = (menuItemId) => {
|
||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns((prev) => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: "menuItemId",
|
||||
headerName: "Menu Item ID",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "menuItemDesc",
|
||||
headerName: "Menu Item Description",
|
||||
width: 250,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "moduleName",
|
||||
headerName: "Module Name",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "main_menu_action_name",
|
||||
headerName: "Main Menu Action",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "main_menu_icon_name",
|
||||
headerName: "Main Menu Icon",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 150,
|
||||
renderCell: ({ row }) => (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="three-dots"
|
||||
onClick={() => handleThreeDotsClick(row.menuItemId)}
|
||||
>
|
||||
<BsThreeDotsVertical
|
||||
className="cursor-pointer text-gray-800 hover:text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
{selectedMenuItem === row.menuItemId && (
|
||||
<div className="absolute bg-white border border-gray-200 shadow-lg p-4 mt-2 rounded-lg">
|
||||
{/* Implement your actions buttons here */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
const openAddEditPopup = (menuItem = {}) => {
|
||||
setIsEditing(!!menuItem.menuItemId);
|
||||
setCurrentMenuItem(menuItem);
|
||||
setParentMenuItemId(isSubMenu ? menuItem.menuItemId : null);
|
||||
setShowAddEditPopup(true);
|
||||
};
|
||||
|
||||
const handleOpenModal = () => setShowModal(true);
|
||||
const handleCloseModal = () => setShowModal(false);
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
console.log("Selected file:", file.name);
|
||||
}
|
||||
};
|
||||
|
||||
const exportToExcel = () => {
|
||||
const worksheet = XLSX.utils.json_to_sheet([]);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "UserDetails");
|
||||
XLSX.writeFile(workbook, "MenuMaintenance.xlsx");
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
const processedValue = name === "status" ? value === "true" : value;
|
||||
setCurrentMenuItem((prev) => ({ ...prev, [name]: processedValue }));
|
||||
};
|
||||
|
||||
const handleSearch = (query) => {
|
||||
setSearchQuery(query);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
if (isEditing) {
|
||||
const updatedItem = await updateMenuItem(currentMenuItem.menuItemId, currentMenuItem);
|
||||
setMenuItems(menuItems.map(item =>
|
||||
item.menuItemId === currentMenuItem.menuItemId ? updatedItem : item
|
||||
));
|
||||
toast.success("Menu item updated successfully!");
|
||||
} else {
|
||||
const addedItem = await addMenuItem(currentMenuItem);
|
||||
setMenuItems([...menuItems, addedItem]);
|
||||
toast.success("Menu item added successfully!");
|
||||
}
|
||||
setShowAddEditPopup(false);
|
||||
} catch (error) {
|
||||
toast.error(`Operation failed: ${error.message}`);
|
||||
console.error("Submit error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (menuItemId) => {
|
||||
if (isSubMenu) {
|
||||
setSubMenuItems(
|
||||
subMenuItems.filter((item) => item.menuItemId !== menuItemId)
|
||||
);
|
||||
} else {
|
||||
await deleteMenuItem(menuItemId);
|
||||
setMenuItems(menuItems.filter((item) => item.menuItemId !== menuItemId));
|
||||
}
|
||||
toast.success("Menu item deleted successfully!");
|
||||
};
|
||||
|
||||
const handleRecordsPerPageChange = (number) => {
|
||||
setRecordsPerPage(number);
|
||||
setPageAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleSubMenuClick = (menuItemId) => {
|
||||
navigate(`/dashboard/setup/sub-menu-maintenance/${menuItemId}`);
|
||||
fetchSubMenuItems(menuItemId);
|
||||
};
|
||||
|
||||
const fetchSubMenuItems = async (menuItemId) => {
|
||||
try {
|
||||
const data = await getSubmenuItems(menuItemId);
|
||||
console.log("Fetched Sub-Menu Items:", data);
|
||||
setSubMenuItems(data);
|
||||
setIsSubMenu(true);
|
||||
setParentMenuItemId(menuItemId);
|
||||
toast.success("Sub-menu items fetched successfully!");
|
||||
} catch (error) {
|
||||
console.error("Error fetching sub-menu items:", error);
|
||||
toast.error("Failed to fetch sub-menu items.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMainMenuClick = (menuId) => {
|
||||
console.log("Selected Menu ID:", menuId);
|
||||
};
|
||||
|
||||
const handleBackToMainMenu = () => {
|
||||
setIsSubMenu(false);
|
||||
setSubMenuItems([]);
|
||||
setParentMenuItemId(null);
|
||||
};
|
||||
|
||||
const totalPages = Math.ceil(menuItems.length / recordsPerPage);
|
||||
const handlePageChange = (event, pageNumber) => {
|
||||
setCurrentPage(pageNumber);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShowAddEditPopup(false);
|
||||
};
|
||||
|
||||
const slicedMenus = (isSubMenu ? subMenuItems : menuItems)
|
||||
.filter(
|
||||
(menuItem) =>
|
||||
menuItem.menuItemDesc &&
|
||||
menuItem.menuItemDesc.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
.slice((currentPage - 1) * recordsPerPage, currentPage * recordsPerPage);
|
||||
|
||||
const handleMenuOpen = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handlePageMenuOpen = (event) => {
|
||||
setPageAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handlePageMenuClose = () => {
|
||||
setPageAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="text-3xl text-center text-black mb-3 bg-slate-400">
|
||||
<Box sx={{ mt: -2 }}>
|
||||
{loading ? (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" height="80vh">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Container maxWidth="xl" sx={{ mt: 5 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
|
||||
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||
Menu Maintenance
|
||||
</div>
|
||||
<Box
|
||||
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2"
|
||||
sx={{ height: 500, width: "100%" }}
|
||||
>
|
||||
<DataGrid
|
||||
rows={menuItems}
|
||||
columns={columns}
|
||||
components={{
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleThreeDotsClick={handleThreeDotsClick}
|
||||
handleModal={() => setIsModalOpen(true)}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} alignItems="center" my={3}>
|
||||
<Grid item xs={12} md={8} lg={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon sx={{ color: '#fff' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
sx: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
border: 'none'
|
||||
}
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiDataGrid-columnHeaders": {
|
||||
backgroundColor: "rgba(107, 114, 128, 0.5)", // Tailwind CSS bg-gray-400 with opacity
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderTitle": {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
maxWidth: 528
|
||||
}}
|
||||
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>
|
||||
{/* Your modals and other components */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="bg-white p-8 rounded-lg shadow-lg">
|
||||
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
|
||||
{/* Modal content here */}
|
||||
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
open={showAddEditPopup}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '80%',
|
||||
maxWidth: 800,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
borderRadius: 1
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||
{isEditing ? "Edit Menu Item" : "Add New Menu Item"}
|
||||
</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box component="form" onSubmit={handleSubmit}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Menu ID*"
|
||||
name="menuId"
|
||||
value={0}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Menu Item Name*"
|
||||
name="menuItemDesc"
|
||||
value={currentMenuItem.menuItemDesc || ""}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Sequence"
|
||||
name="itemSeq"
|
||||
type="number"
|
||||
value={currentMenuItem.itemSeq || ""}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Module Name"
|
||||
name="moduleName"
|
||||
value={currentMenuItem.moduleName || ""}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Menu Action Link"
|
||||
name="main_menu_action_name"
|
||||
value={currentMenuItem.main_menu_action_name || ""}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Menu Icon Name"
|
||||
name="main_menu_icon_name"
|
||||
value={currentMenuItem.main_menu_icon_name || ""}
|
||||
onChange={handleInputChange}
|
||||
placeholder="e.g., fa-home"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Status</InputLabel>
|
||||
<Select
|
||||
name="status"
|
||||
value={currentMenuItem.status ? "true" : "false"}
|
||||
onChange={handleInputChange}
|
||||
label="Status"
|
||||
>
|
||||
<MenuItem value="true">Enable</MenuItem>
|
||||
<MenuItem value="false">Disable</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3 }}>
|
||||
<Button onClick={handleClose} sx={{ mr: 2 }}>Cancel</Button>
|
||||
<Button type="submit" variant="contained">
|
||||
{isEditing ? "UPDATE" : "ADD"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Container>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FaUser, FaUsers, FaUtensils, FaLock, FaCogs, FaKey } from 'react-icons/fa';
|
||||
import UserMaintance from './UserMaintance';
|
||||
import UserGroupMaintance from './UserGroupMaintance/UserGroupMaintance';
|
||||
import MenuMaintance from './MenuMaintance/MenuMaintance';
|
||||
import MenuAccessControl from './MenuAccessControl/MenuAccessControl';
|
||||
import SystemParameters from './SystemParameters/SystemParameters';
|
||||
import ApiRegistery from './ApiRegistery/ApiRegistery';
|
||||
import TokenRegistery from './TokenRegistery/TokenRegistery';
|
||||
import Codeextension from './Codeextension.js';
|
||||
import DynamicTable from './Dynamictable.js';
|
||||
import React from 'react';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import CardList from './CardList'; // Import the CardList component
|
||||
|
||||
const Card = ({ title, content, icon: Icon, onClick }) => (
|
||||
<div onClick={onClick} className="bg-white border border-gray-300 rounded-lg shadow-md p-6 m-4 cursor-pointer transition-transform transform hover:scale-105 hover:shadow-lg flex flex-col items-center justify-center text-center">
|
||||
<Icon className="text-4xl text-gray-800 mb-4" />
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<p className="text-gray-600">{content}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const CardList = () => {
|
||||
const [showUserMaintance, setShowUserMaintance] = useState(false);
|
||||
const [showUserGroupMaintance, setShowUserGroupMaintance] = useState(false);
|
||||
const [showMenuMaintance, setShowMenuMaintance] = useState(false);
|
||||
const [showMenuAccessControl, setShowMenuAccessControl] = useState(false);
|
||||
const [showSystemParameters, setShowSystemParameters] = useState(false);
|
||||
// const [showAccessType, setShowAccessType] = useState(false);
|
||||
const [showApiRegistery, setShowApiRegistery] = useState(false);
|
||||
const [showTokenRegistery, setShowTokenRegistery] = useState(false);
|
||||
const [showCodeExtension, setShowCodeExtension] = useState(false);
|
||||
const [showDynamicTable, setShowDynamicTable] = useState(false);
|
||||
|
||||
const handleCardClick = (menuItemDesc) => {
|
||||
setShowUserMaintance(menuItemDesc === 'User Maintance');
|
||||
setShowUserGroupMaintance(menuItemDesc === 'User Group Maintance');
|
||||
setShowMenuMaintance(menuItemDesc === 'Menu Maintance');
|
||||
setShowMenuAccessControl(menuItemDesc === 'Menu Access Control');
|
||||
setShowSystemParameters(menuItemDesc === 'System Parameters');
|
||||
// setShowAccessType(menuItemDesc === 'Access Type');
|
||||
setShowApiRegistery(menuItemDesc === 'Api Registery');
|
||||
setShowTokenRegistery(menuItemDesc === 'Token Registery');
|
||||
setShowCodeExtension(menuItemDesc === 'Code Extension');
|
||||
setShowDynamicTable(menuItemDesc === 'Dynamic Table');
|
||||
};
|
||||
const Setup = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!showUserMaintance && !showUserGroupMaintance && !showMenuMaintance && !showMenuAccessControl && !showSystemParameters && !showApiRegistery && !showTokenRegistery && !showCodeExtension && !showDynamicTable && (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100 p-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
<Card title="User Maintance" content="Manage users" icon={FaUser} onClick={() => handleCardClick('User Maintance')} />
|
||||
<Card title="User Group Maintance" content="Manage user groups" icon={FaUsers} onClick={() => handleCardClick('User Group Maintance')} />
|
||||
<Card title="Menu Maintance" content="Manage menus" icon={FaUtensils} onClick={() => handleCardClick('Menu Maintance')} />
|
||||
<Card title="Menu Access Control" content="Control menu access" icon={FaLock} onClick={() => handleCardClick('Menu Access Control')} />
|
||||
<Card title="System Parameters" content="Configure system parameters" icon={FaCogs} onClick={() => handleCardClick('System Parameters')} />
|
||||
{/* <Card title="Access Type" content="Manage access types" icon={FaKey} onClick={() => handleCardClick('Access Type')} /> */}
|
||||
<Card title="Api Registery" content="Manage APIs" icon={FaUser} onClick={() => handleCardClick('Api Registery')} />
|
||||
<Card title="Token Registery" content="Manage tokens" icon={FaKey} onClick={() => handleCardClick('Token Registery')} />
|
||||
<Card title="Code Extension" content="Extend code functionalities" icon={FaLock} onClick={() => handleCardClick('Code Extension')} />
|
||||
<Card title="Dynamic Table" content="Dynamic data tables" icon={FaKey} onClick={() => handleCardClick('Dynamic Table')} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '20px' }}>
|
||||
|
||||
{/* Show CardList when at /dashboard/setup */}
|
||||
{window.location.pathname === '/dashboard/setup' ? (
|
||||
<CardList />
|
||||
) : (
|
||||
<Outlet />
|
||||
)}
|
||||
{showUserMaintance && <UserMaintance />}
|
||||
{showUserGroupMaintance && <UserGroupMaintance />}
|
||||
{showMenuMaintance && <MenuMaintance />}
|
||||
{showMenuAccessControl && <MenuAccessControl />}
|
||||
{showSystemParameters && <SystemParameters />}
|
||||
{/* {showAccessType && <AccessType />} */}
|
||||
{showApiRegistery && <ApiRegistery />}
|
||||
{showTokenRegistery && <TokenRegistery />}
|
||||
{showCodeExtension && <Codeextension />}
|
||||
{showDynamicTable && <DynamicTable />}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardList;
|
||||
export default Setup;
|
||||
@@ -1,35 +1,59 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Container, Grid, TextField } from "@mui/material";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Grid,
|
||||
TextField,
|
||||
Typography,
|
||||
Paper,
|
||||
Box,
|
||||
LinearProgress
|
||||
} from "@mui/material";
|
||||
import { toast } from "react-toastify";
|
||||
import { addSysParameter } from "../../../ApiServices/SytemparameterApi";
|
||||
import { useSystemParameters } from "../../../context/SystemParameterContext";
|
||||
|
||||
const SystemParameterForm = () => {
|
||||
const { systemParameters, loading: contextLoading } = useSystemParameters();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
schedulerTimer: "",
|
||||
schedulerTime: "",
|
||||
leaseTaxCode: "",
|
||||
vesselConfirmationProcessLimit: "",
|
||||
vesselConfProcessLimit: "",
|
||||
rowToDisplay: "",
|
||||
linkToDisplay: "",
|
||||
rowToAdd: "",
|
||||
lovRowToDisplay: "",
|
||||
lovLinkToDisplay: "",
|
||||
oldServerName: "",
|
||||
oldBase: "",
|
||||
oldAdminUser: "",
|
||||
oldServerPort: "",
|
||||
oidserverName: "",
|
||||
oidBase: "",
|
||||
oidAdminUser: "",
|
||||
oidServerPort: "",
|
||||
userDefaultGroup: "",
|
||||
defaultDepartment: "",
|
||||
defaultPosition: "",
|
||||
singleCharge: "",
|
||||
firstDayOfWeek: "",
|
||||
firstDayOftheWeek: "",
|
||||
hourPerShift: "",
|
||||
cnBillingFrequency: "",
|
||||
billingDepartmentCode: "",
|
||||
basePriceList: "",
|
||||
nonContainerServiceOrderAutoApprovalDeptCode: "",
|
||||
ediMAESchedulerOnOff: "",
|
||||
ediSchedulerOnOff: "",
|
||||
companyDisplayName: "",
|
||||
nonContainerServiceOrder: "",
|
||||
ediMaeSchedulerONOFF: "",
|
||||
ediSchedulerONOFF: "",
|
||||
upload_Logo: null,
|
||||
company_Display_Name: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (systemParameters) {
|
||||
setFormData((prevForm) => ({
|
||||
...prevForm,
|
||||
...systemParameters,
|
||||
}));
|
||||
}
|
||||
}, [systemParameters]);
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setFormData((prevState) => ({
|
||||
@@ -41,86 +65,153 @@ const SystemParameterForm = () => {
|
||||
const handleFileChange = (event) => {
|
||||
setFormData((prevState) => ({
|
||||
...prevState,
|
||||
logo: event.target.files[0],
|
||||
upload_Logo: event.target.files[0],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
alert("Form submitted successfully!");
|
||||
|
||||
try {
|
||||
console.log("Form Data:", formData);
|
||||
const sysParamData = await addSysParameter(formData);
|
||||
console.log("API Response:", sysParamData);
|
||||
toast.success("Form submitted successfully!");
|
||||
} catch (error) {
|
||||
console.error("Error:", error.response ? error.response.data : error.message);
|
||||
toast.error("Failed to submit the form. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setFormData({
|
||||
schedulerTimer: "",
|
||||
schedulerTime: "",
|
||||
leaseTaxCode: "",
|
||||
vesselConfirmationProcessLimit: "",
|
||||
vesselConfProcessLimit: "",
|
||||
rowToDisplay: "",
|
||||
linkToDisplay: "",
|
||||
rowToAdd: "",
|
||||
lovRowToDisplay: "",
|
||||
lovLinkToDisplay: "",
|
||||
oldServerName: "",
|
||||
oldBase: "",
|
||||
oldAdminUser: "",
|
||||
oldServerPort: "",
|
||||
oidserverName: "",
|
||||
oidBase: "",
|
||||
oidAdminUser: "",
|
||||
oidServerPort: "",
|
||||
userDefaultGroup: "",
|
||||
defaultDepartment: "",
|
||||
defaultPosition: "",
|
||||
singleCharge: "",
|
||||
firstDayOfWeek: "",
|
||||
firstDayOftheWeek: "",
|
||||
hourPerShift: "",
|
||||
cnBillingFrequency: "",
|
||||
billingDepartmentCode: "",
|
||||
basePriceList: "",
|
||||
nonContainerServiceOrderAutoApprovalDeptCode: "",
|
||||
ediMAESchedulerOnOff: "",
|
||||
ediSchedulerOnOff: "",
|
||||
companyDisplayName: "",
|
||||
nonContainerServiceOrder: "",
|
||||
ediMaeSchedulerONOFF: "",
|
||||
ediSchedulerONOFF: "",
|
||||
upload_Logo: null,
|
||||
company_Display_Name: "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="mt-5">
|
||||
<p className="bg-gray-400 text-center text-white text-3xl m-3 p-2">
|
||||
System Parameters
|
||||
</p>
|
||||
<form onSubmit={handleSubmit} className="m-5 p-5">
|
||||
<Grid container spacing={3}>
|
||||
{Object.keys(formData).map((key, index) => (
|
||||
<Grid item xs={12} sm={6} key={index}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={key
|
||||
const formatLabel = (key) => {
|
||||
return key
|
||||
.split(/(?=[A-Z])/)
|
||||
.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}
|
||||
value={formData[key]}
|
||||
onChange={handleInputChange}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<input type="file" onChange={handleFileChange} />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment key={index}>
|
||||
<Grid item xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography>Upload Logo</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
InputProps={{
|
||||
inputProps: {
|
||||
accept: "image/*"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<div style={{ textAlign: "end", marginTop: 20 }}>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 4 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
style={{ marginRight: 10 }}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="outlined" color="primary" onClick={handleClear}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleClear}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</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 { Box, Button } from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Pagination,
|
||||
IconButton,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Chip,
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
Divider,
|
||||
Grid,
|
||||
TextareaAutosize,
|
||||
Popover,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContentText,
|
||||
CircularProgress
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Edit as EditIcon,
|
||||
Delete as DeleteIcon,
|
||||
Add as AddIcon,
|
||||
Menu as MenuIcon,
|
||||
Close as CloseIcon,
|
||||
Search as SearchIcon,
|
||||
LibraryBooks as LibraryBooksIcon,
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from "@mui/icons-material";
|
||||
import { toast } from "react-toastify";
|
||||
import * as tokenRegistryAPI from './tokenregistryapi';
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleModal }) {
|
||||
const handleGoToPage1 = () => {
|
||||
if (apiRef.current) {
|
||||
apiRef.current.setPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer className="flex flex-wrap justify-between p-2 bg-gray-100 border-b border-gray-200">
|
||||
<Button
|
||||
onClick={handleGoToPage1}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 m-1"
|
||||
>
|
||||
Go to page 1
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleModal}
|
||||
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600 m-1"
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function TokenRegistry() {
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
||||
const [, setIsModalOpen] = useState(false);
|
||||
const apiRef = useRef(null);
|
||||
function TOKENRegistry() {
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [showAddEditModal, setShowAddEditModal] = useState(false);
|
||||
const [showGenerateTokenModal, setShowGenerateTokenModal] = useState(false);
|
||||
const [newTokenName, setNewTokenName] = useState("");
|
||||
const [generatedToken, setGeneratedToken] = useState("");
|
||||
const [currentToken, setCurrentToken] = useState({
|
||||
id: "",
|
||||
tokenName: "",
|
||||
tokenValue: "",
|
||||
isActive: true,
|
||||
scopes: []
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||
const [visibleColumns, setVisibleColumns] = useState({
|
||||
id: true,
|
||||
tokenName: true,
|
||||
tokenValue: true,
|
||||
scopes: true,
|
||||
isActive: true,
|
||||
actions: true
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedScope, setSelectedScope] = useState("");
|
||||
const [selectedScopes, setSelectedScopes] = useState([]);
|
||||
const [availableScopes] = useState([
|
||||
{ value: 'read', label: 'Read Access' },
|
||||
{ value: 'write', label: 'Write Access' },
|
||||
{ value: 'delete', label: 'Delete Access' },
|
||||
{ value: 'admin', label: 'Admin Access' },
|
||||
]);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [tokenToDelete, setTokenToDelete] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("token"); // Get token from local storage
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${api}/apiregistery/getall`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
setMenuItems(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
fetchTokens();
|
||||
}, []);
|
||||
|
||||
const handleThreeDotsClick = (menuItemId) => {
|
||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
||||
const fetchTokens = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await tokenRegistryAPI.fetchAllTokens();
|
||||
setTokens(data);
|
||||
} catch (error) {
|
||||
handleApiError(error, "fetch tokens");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: "table_id",
|
||||
headerName: "Table ID",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "token_name",
|
||||
headerName: "Token Name",
|
||||
width: 250,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "token",
|
||||
headerName: "Token",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 150,
|
||||
renderCell: ({ row }) => (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleThreeDotsClick(row.id)}
|
||||
>
|
||||
<BsThreeDotsVertical /> {/* Updated icon from react-icons */}
|
||||
</div>
|
||||
{selectedMenuItem === row.id && (
|
||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Edit
|
||||
</button>
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
const handleApiError = (error, action) => {
|
||||
if (error.message === 'Unauthorized') {
|
||||
toast.error("Your session has expired. Please log in again.");
|
||||
} else {
|
||||
toast.error(`Failed to ${action}`);
|
||||
console.error(`Error ${action}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns(prev => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value, type, checked } = event.target;
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSearch = (query) => {
|
||||
setSearchQuery(query);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShowAddEditModal(false);
|
||||
setShowGenerateTokenModal(false);
|
||||
setGeneratedToken("");
|
||||
setNewTokenName("");
|
||||
setSelectedScopes([]);
|
||||
};
|
||||
|
||||
const handleRecordsPerPageChange = (number) => {
|
||||
setRecordsPerPage(number);
|
||||
setCurrentPage(1);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const filteredTokens = tokens.filter(
|
||||
(item) =>
|
||||
item.tokenName &&
|
||||
item.tokenName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(filteredTokens.length / recordsPerPage);
|
||||
const handlePageChange = (event, pageNumber) => {
|
||||
setCurrentPage(pageNumber);
|
||||
};
|
||||
|
||||
const slicedTokens = filteredTokens.slice(
|
||||
(currentPage - 1) * recordsPerPage,
|
||||
currentPage * recordsPerPage
|
||||
);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
if (isEditing) {
|
||||
await tokenRegistryAPI.updateToken(currentToken.id, currentToken);
|
||||
toast.success("Token updated successfully!");
|
||||
} else {
|
||||
await tokenRegistryAPI.createToken(currentToken);
|
||||
toast.success("Token added successfully!");
|
||||
}
|
||||
setShowAddEditModal(false);
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, isEditing ? "update token" : "add token");
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = (token = { id: "", tokenName: "", tokenValue: "", isActive: false, scopes: [] }) => {
|
||||
setIsEditing(!!token.id);
|
||||
setCurrentToken(token);
|
||||
setSelectedScopes(token.scopes || []);
|
||||
setShowAddEditModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id) => {
|
||||
setTokenToDelete(id);
|
||||
setDeleteConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
try {
|
||||
await tokenRegistryAPI.deleteToken(tokenToDelete);
|
||||
toast.success('Token deleted successfully');
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, "delete token");
|
||||
} finally {
|
||||
setDeleteConfirmOpen(false);
|
||||
setTokenToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
const addScope = () => {
|
||||
if (selectedScope && !selectedScopes.includes(selectedScope)) {
|
||||
setSelectedScopes([...selectedScopes, selectedScope]);
|
||||
setSelectedScope("");
|
||||
}
|
||||
};
|
||||
|
||||
const removeScope = (scopeToRemove) => {
|
||||
setSelectedScopes(selectedScopes.filter(scope => scope !== scopeToRemove));
|
||||
};
|
||||
|
||||
const generateNewToken = async () => {
|
||||
if (!newTokenName.trim()) {
|
||||
toast.error("Please enter a token name");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await tokenRegistryAPI.generateToken({
|
||||
name: newTokenName,
|
||||
scopes: selectedScopes
|
||||
});
|
||||
setGeneratedToken(data.tokenValue);
|
||||
toast.success("Token generated successfully!");
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, "generate token");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleColumnsMenuClick = (event) => {
|
||||
setColumnsAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleColumnsMenuClose = () => {
|
||||
setColumnsAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mt-5 px-2 md:px-0">
|
||||
<Box className="w-full max-w-7xl bg-gray-50 p-4 md:p-6 rounded shadow-md">
|
||||
<p className="text-2xl md:text-3xl text-center text-white bg-gray-400 mb-4 p-3">
|
||||
Token Registry
|
||||
</p>
|
||||
<div className="bg-white p-2 md:p-4 rounded shadow-md">
|
||||
<DataGrid
|
||||
rows={menuItems}
|
||||
columns={columns}
|
||||
components={{
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleModal={() => setIsModalOpen(true)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
}}
|
||||
className="data-grid"
|
||||
/>
|
||||
</div>
|
||||
<Box sx={{ marginTop: "1rem", padding: 2 }}>
|
||||
{loading ? (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
{/* Token Generation Card */}
|
||||
<Card sx={{ mb: 4, boxShadow: 3 }}>
|
||||
<CardHeader
|
||||
title="Token Management"
|
||||
action={
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setShowGenerateTokenModal(true)}
|
||||
sx={{ backgroundColor: '#1976d2', color: 'white' }}
|
||||
>
|
||||
Generate New Token
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography variant="body1">
|
||||
Manage your API tokens here. Generate new tokens or delete existing ones.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
|
||||
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||
Token Registry
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} alignItems="center" mb={3}>
|
||||
<Grid item xs={12} md={8} lg={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<SearchIcon sx={{ color: 'action.active', mr: 1 }} />
|
||||
),
|
||||
sx: {
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4} lg={6} display="flex" justifyContent="flex-end">
|
||||
<IconButton
|
||||
onClick={() => openModal()}
|
||||
color="primary"
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleColumnsMenuClick}
|
||||
color="primary"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
|
||||
<Table>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5' }}>
|
||||
<TableRow>
|
||||
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
|
||||
<TableCell key={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{slicedTokens.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={Object.keys(visibleColumns).filter(key => visibleColumns[key]).length}
|
||||
align="center"
|
||||
>
|
||||
No Data Available
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
slicedTokens.map((token, index) => (
|
||||
<TableRow key={index} hover>
|
||||
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
|
||||
<TableCell key={key}>
|
||||
{key === "actions" ? (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => openModal(token)}
|
||||
color="primary"
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => handleDeleteClick(token.id)}
|
||||
color="error"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : key === "isActive" ? (
|
||||
<Chip
|
||||
label={token.isActive ? "Active" : "Inactive"}
|
||||
color={token.isActive ? "success" : "error"}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: token.isActive ? '#d4f7d4' : '#f7d4d4',
|
||||
color: token.isActive ? 'green' : 'red'
|
||||
}}
|
||||
/>
|
||||
) : key === "scopes" ? (
|
||||
<Box>
|
||||
{token.scopes && token.scopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
size="small"
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
) : (
|
||||
token[key]
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Manage Columns */}
|
||||
<Popover
|
||||
open={Boolean(columnsAnchorEl)}
|
||||
anchorEl={columnsAnchorEl}
|
||||
onClose={handleColumnsMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{Object.keys(visibleColumns).map((column) => (
|
||||
<ListItem key={column} dense button onClick={() => toggleColumn(column)}>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={visibleColumns[column]}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={column.charAt(0).toUpperCase() + column.slice(1).toLowerCase()} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Popover>
|
||||
|
||||
{/* Records Per Page */}
|
||||
<Box display="flex" justifyContent="flex-end" mt={2}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<LibraryBooksIcon />}
|
||||
onClick={handleMenuClick}
|
||||
sx={{ border: '2px solid', borderRadius: '8px', boxShadow: 1 }}
|
||||
>
|
||||
{recordsPerPage}
|
||||
</Button>
|
||||
<Popover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{[1, 5, 10, 20, 50].map((number) => (
|
||||
<ListItem
|
||||
key={number}
|
||||
dense
|
||||
button
|
||||
onClick={() => handleRecordsPerPageChange(number)}
|
||||
sx={{ minWidth: '100px' }}
|
||||
>
|
||||
<ListItemText primary={number} />
|
||||
{recordsPerPage === number && <CheckCircleIcon color="primary" />}
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Popover>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="center" mt={2}>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={currentPage}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Generate Token Modal */}
|
||||
<Dialog open={showGenerateTokenModal} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
Generate New Token
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Name"
|
||||
value={newTokenName}
|
||||
onChange={(e) => setNewTokenName(e.target.value)}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Token Scopes
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center" mb={2}>
|
||||
<FormControl fullWidth sx={{ mr: 2 }}>
|
||||
<InputLabel>Select a scope</InputLabel>
|
||||
<Select
|
||||
value={selectedScope}
|
||||
onChange={(e) => setSelectedScope(e.target.value)}
|
||||
label="Select a scope"
|
||||
>
|
||||
<MenuItem value=""><em>Select a scope</em></MenuItem>
|
||||
{availableScopes.map(scope => (
|
||||
<MenuItem key={scope.value} value={scope.value}>
|
||||
{scope.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={addScope}
|
||||
disabled={!selectedScope}
|
||||
>
|
||||
Add Scope
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Select and add the permissions this token should have
|
||||
</Typography>
|
||||
|
||||
{/* Display selected scopes as chips */}
|
||||
{selectedScopes.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{selectedScopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
onDelete={() => removeScope(scope)}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{generatedToken && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Generated Token
|
||||
</Typography>
|
||||
<TextareaAutosize
|
||||
minRows={3}
|
||||
value={generatedToken}
|
||||
readOnly
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', borderColor: '#ccc' }}
|
||||
/>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Copy this token and store it securely. You won't be able to see it again.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={generateNewToken}
|
||||
disabled={!newTokenName.trim()}
|
||||
>
|
||||
Generate Token
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Add/Edit Token Modal */}
|
||||
<Dialog open={showAddEditModal} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
{isEditing ? "Edit Token" : "Add Token"}
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Name"
|
||||
name="tokenName"
|
||||
value={currentToken.tokenName}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Value"
|
||||
name="tokenValue"
|
||||
value={currentToken.tokenValue}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Token Scopes
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center" mb={2}>
|
||||
<FormControl fullWidth sx={{ mr: 2 }}>
|
||||
<InputLabel>Select a scope</InputLabel>
|
||||
<Select
|
||||
value={selectedScope}
|
||||
onChange={(e) => setSelectedScope(e.target.value)}
|
||||
label="Select a scope"
|
||||
>
|
||||
<MenuItem value=""><em>Select a scope</em></MenuItem>
|
||||
{availableScopes.map(scope => (
|
||||
<MenuItem key={scope.value} value={scope.value}>
|
||||
{scope.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
if (selectedScope && !currentToken.scopes.includes(selectedScope)) {
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
scopes: [...prev.scopes, selectedScope]
|
||||
}));
|
||||
setSelectedScope("");
|
||||
}
|
||||
}}
|
||||
disabled={!selectedScope}
|
||||
>
|
||||
Add Scope
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Select and add the permissions this token should have
|
||||
</Typography>
|
||||
|
||||
{/* Display selected scopes as chips */}
|
||||
{currentToken.scopes && currentToken.scopes.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{currentToken.scopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
onDelete={() => {
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
scopes: prev.scopes.filter(s => s !== scope)
|
||||
}));
|
||||
}}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={currentToken.isActive}
|
||||
onChange={handleInputChange}
|
||||
name="isActive"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Active?"
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
type="submit"
|
||||
>
|
||||
{isEditing ? "Update Token" : "Add Token"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={deleteConfirmOpen}
|
||||
onClose={() => setDeleteConfirmOpen(false)}
|
||||
>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this token?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteConfirmOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleDeleteConfirm} color="error" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{/* 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 {
|
||||
TextField,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
|
||||
const UpdateModal = ({ user, onUpdate, onClose }) => {
|
||||
const [updatedUser, setUpdatedUser] = useState(() => ({ ...user }));
|
||||
const UpdateModal = ({ user, onUpdate, onClose, open }) => {
|
||||
const [updatedUser, setUpdatedUser] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Initialize form with user data when modal opens or user changes
|
||||
useEffect(() => {
|
||||
console.log('Updated user state:', updatedUser);
|
||||
}, [updatedUser]);
|
||||
if (user) {
|
||||
setUpdatedUser({ ...user });
|
||||
}
|
||||
}, [user, open]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updatedData = { ...updatedUser, [field]: value };
|
||||
setUpdatedUser(updatedData);
|
||||
setUpdatedUser(prev => ({
|
||||
...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();
|
||||
} catch (err) {
|
||||
console.error('Update failed:', err);
|
||||
setError(err.message || 'Failed to update user');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// const handleUpdate = () => {
|
||||
// console.log('Before update:', updatedUser);
|
||||
// onUpdate(updatedUser);
|
||||
|
||||
// onClose();
|
||||
|
||||
|
||||
// // Use a callback function with setUpdatedUser to ensure the state is updated
|
||||
// setUpdatedUser((prev) => {
|
||||
// const updatedData = { ...prev };
|
||||
// console.log('Updated data:', updatedData);
|
||||
|
||||
// onUpdate(updatedData); // Pass the updatedData to onUpdate
|
||||
// onClose();
|
||||
// return updatedData; // Return the updatedData to setUpdatedUser
|
||||
// });
|
||||
|
||||
// // Set the updatedUser state directly to ensure the UI reflects the changes
|
||||
// setUpdatedUser(updatedUser);
|
||||
// };
|
||||
if (!user) {
|
||||
return null; // Or render a loading state/message
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modalWrapper">
|
||||
<div className="modal">
|
||||
<button className="closeBtn" onClick={onClose}>
|
||||
X
|
||||
</button>
|
||||
<div>User ID: {user.userId}</div>
|
||||
<div>Username: {user.username}</div>
|
||||
<div>Full Name: {user.fullName}</div>
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Update User</DialogTitle>
|
||||
<DialogContent>
|
||||
{error && (
|
||||
<div style={{ color: 'red', marginBottom: '16px' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>Email: {user.email}</div>
|
||||
|
||||
<div>Mob Number: {user.mobno}</div>
|
||||
<div>User grp name: {user.usergrpname}</div>
|
||||
|
||||
<label htmlFor="updatedUserId">User ID:</label>
|
||||
<input
|
||||
id="updatedUserId"
|
||||
type="text"
|
||||
value={updatedUser.userId}
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="User ID"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={updatedUser.userId || ''}
|
||||
onChange={(e) => handleChange('userId', e.target.value)}
|
||||
disabled // Typically IDs shouldn't be editable
|
||||
/>
|
||||
|
||||
<label htmlFor="updatedUsername">Username:</label>
|
||||
<input
|
||||
id="updatedUsername"
|
||||
type="text"
|
||||
value={updatedUser.username}
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Username"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={updatedUser.username || ''}
|
||||
onChange={(e) => handleChange('username', e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<label htmlFor="updatedEmail">Email:</label>
|
||||
<input
|
||||
id="updatedEmail"
|
||||
type="text"
|
||||
value={updatedUser.email}
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Full Name"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={updatedUser.fullName || ''}
|
||||
onChange={(e) => handleChange('fullName', e.target.value)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Email"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
type="email"
|
||||
value={updatedUser.email || ''}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
/>
|
||||
|
||||
<label htmlFor="updatedFullName">Full Name:</label>
|
||||
<input
|
||||
id="updatedFullName"
|
||||
type="text"
|
||||
value={updatedUser.fullName}
|
||||
onChange={(e) => handleChange('fullName', e.target.value)}
|
||||
/>
|
||||
<label htmlFor="updatedMobno">Mob No:</label>
|
||||
<input
|
||||
id="updatedmobno"
|
||||
type="number"
|
||||
value={updatedUser.mobno}
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Mobile Number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={updatedUser.mobno || ''}
|
||||
onChange={(e) => handleChange('mobno', e.target.value)}
|
||||
/>
|
||||
<label htmlFor="updatedusergrpname">User grp name:</label>
|
||||
<input
|
||||
id="updatedusergrpname"
|
||||
type="text"
|
||||
value={updatedUser.usergrpname}
|
||||
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="User Group"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={updatedUser.usergrpname || ''}
|
||||
onChange={(e) => handleChange('usergrpname', e.target.value)}
|
||||
/>
|
||||
|
||||
<button onClick={handleSave}>SAVE</button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : 'Save'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,3 +5,129 @@
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* UserGroupMaintenance.css */
|
||||
|
||||
.user-group-maintenance-container {
|
||||
padding: 2rem;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-family: 'PT Serif', serif;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.data-grid-container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.grid-toolbar {
|
||||
padding: 0.5rem 0;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
font-family: 'PT Serif', serif;
|
||||
text-transform: capitalize;
|
||||
font-weight: 600;
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background-color: #1565c0;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
background-color: #388e3c;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
font-family: 'PT Serif', serif;
|
||||
font-weight: 700;
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.custom-cell {
|
||||
font-family: 'PT Serif', serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.three-dots {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.popover {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.popover button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-family: 'PT Serif', serif;
|
||||
}
|
||||
|
||||
.popover button:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-family: 'PT Serif', serif;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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 { BsThreeDotsVertical } from "react-icons/bs";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
import "./UserGroupMaintance.css";
|
||||
// eslint-disable-next-line
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleModal }) {
|
||||
@@ -14,9 +15,21 @@ function CustomToolbar({ apiRef, handleModal }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer>
|
||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
||||
<Button onClick={handleModal}>+</Button>
|
||||
<GridToolbarContainer className="grid-toolbar">
|
||||
<Button
|
||||
variant="contained"
|
||||
className="toolbar-button"
|
||||
onClick={handleGoToPage1}
|
||||
>
|
||||
Go to page 1
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
className="toolbar-button add-button"
|
||||
onClick={handleModal}
|
||||
>
|
||||
Add User Group
|
||||
</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
@@ -26,7 +39,7 @@ function UserMaintance() {
|
||||
const [selectedUserGroup, setSelectedUserGroup] = useState(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const apiRef = useRef(null);
|
||||
// eslint-disable-next-line
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("token");
|
||||
@@ -136,38 +149,21 @@ function UserMaintance() {
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "createdateformated",
|
||||
headerName: "Create Date Formated",
|
||||
width: 100,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "updatedateformated",
|
||||
headerName: "Update Date Formated",
|
||||
width: 100,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
|
||||
// Add other columns as needed
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 150,
|
||||
renderCell: ({ row }) => (
|
||||
<div>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div
|
||||
className="three-dots"
|
||||
onClick={() => handleThreeDotsClick(row.usrGrp)}
|
||||
>
|
||||
<BsThreeDotsVertical />
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</div>
|
||||
{selectedUserGroup === row.usrGrp && (
|
||||
<div className="popover">
|
||||
<button onClick={() => handleDelete(row.usrGrp)}>Delete</button>
|
||||
{/* You can include other actions here */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -176,15 +172,12 @@ function UserMaintance() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4">
|
||||
<div className="text-center text-3xl text-white bg-gray-400 p-2 rounded-lg">
|
||||
<div className="user-group-maintenance-container">
|
||||
<Typography variant="h4" className="main-title" gutterBottom>
|
||||
User Group Maintenance
|
||||
</div>
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
className="w-full p-4 md:w-3/4 lg:w-2/3 xl:w-1/2 bg-white border border-gray-200 shadow-lg rounded-lg"
|
||||
sx={{ height: 500, width: "100%" }}
|
||||
>
|
||||
<Box className="data-grid-container" sx={{ height: 500, width: '100%' }}>
|
||||
<DataGrid
|
||||
rows={userGroups}
|
||||
columns={columns}
|
||||
@@ -192,25 +185,34 @@ function UserMaintance() {
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleThreeDotsClick={handleThreeDotsClick}
|
||||
handleModal={handleModal}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
rowsPerPageOptions={[10]}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
}}
|
||||
className="bg-gray-400"
|
||||
/>
|
||||
</Box>
|
||||
{/* Your modals and other components */}
|
||||
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
||||
<div className="bg-white p-8 rounded-lg shadow-lg max-w-lg w-full">
|
||||
<h2 className="text-xl font-bold mb-4">Modal Title</h2>
|
||||
{/* Modal content here */}
|
||||
<Button onClick={() => setIsModalOpen(false)}>Close</Button>
|
||||
<div className="modal-overlay">
|
||||
<div className="modal-content">
|
||||
<Typography variant="h5" className="modal-title">
|
||||
Add New User Group
|
||||
</Typography>
|
||||
{/* Modal form content here */}
|
||||
<div className="modal-actions">
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
className="toolbar-button"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,168 +1,499 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs";
|
||||
import Modal from "./Modal"; // Import your Modal component
|
||||
import UpdateModal from "./UpdateModal"; // Import your UpdateModal component
|
||||
import "./UserMaintance.css";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
TextField,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Pagination,
|
||||
Modal,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
FormGroup,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
Select,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Add,
|
||||
Search,
|
||||
Settings,
|
||||
Edit,
|
||||
Delete,
|
||||
Download,
|
||||
Upload,
|
||||
Close,
|
||||
Visibility,
|
||||
VisibilityOff,
|
||||
Check,
|
||||
} from "@mui/icons-material";
|
||||
import { toast } from "react-toastify";
|
||||
import * as XLSX from "xlsx";
|
||||
import { getAllUsers, createUser, updateUser, deleteUser } from "../../ApiServices/Usermaintenanceapi";
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleThreeDotsClick, handleModal }) {
|
||||
const handleGoToPage1 = () => {
|
||||
if (apiRef.current) {
|
||||
apiRef.current.setPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer>
|
||||
<Button onClick={handleGoToPage1}>Go to page 1</Button>
|
||||
<Button onClick={handleModal}>+</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function UserMaintance() {
|
||||
function UserMaintenanceView() {
|
||||
// State management
|
||||
const [users, setUsers] = useState([]);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
const [selectedUserForUpdate, setSelectedUserForUpdate] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false);
|
||||
const [newUser, setNewUser] = useState({
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [columnMenuAnchor, setColumnMenuAnchor] = useState(null);
|
||||
|
||||
// User data state
|
||||
const [userData, setUserData] = useState({
|
||||
userId: "",
|
||||
username: "",
|
||||
fullName: "",
|
||||
email: "",
|
||||
|
||||
usrGrpName: "",
|
||||
mob_no: "",
|
||||
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(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("token");
|
||||
console.log("object", token);
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await fetch(`${api}/api/getAllAppUser`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const usersWithIds = data.map((user, index) => ({
|
||||
...user,
|
||||
id: index + 1,
|
||||
}));
|
||||
setUsers(usersWithIds);
|
||||
setLoading(true);
|
||||
const response = await getAllUsers();
|
||||
setUsers(response.data || []);
|
||||
} 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) => {
|
||||
setSelectedUser(userId === selectedUser ? null : userId);
|
||||
// Filter users based on search query
|
||||
const filteredUsers = users.filter((user) =>
|
||||
user.username.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
// Pagination logic
|
||||
const totalPages = Math.ceil(filteredUsers.length / recordsPerPage);
|
||||
const paginatedUsers = filteredUsers.slice(
|
||||
(currentPage - 1) * recordsPerPage,
|
||||
currentPage * recordsPerPage
|
||||
);
|
||||
|
||||
// Column toggle handler
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns((prev) => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDelete = (userId) => {
|
||||
console.log("Delete user with ID:", userId);
|
||||
};
|
||||
|
||||
const handleUpdate = (user) => {
|
||||
setSelectedUserForUpdate(user);
|
||||
setIsUpdateModalOpen(true);
|
||||
};
|
||||
|
||||
const handleModal = () => {
|
||||
// Modal handlers
|
||||
const handleOpenModal = (user = null) => {
|
||||
if (user) {
|
||||
setUserData(user);
|
||||
setIsEditing(true);
|
||||
} else {
|
||||
setUserData({
|
||||
userId: "",
|
||||
username: "",
|
||||
fullName: "",
|
||||
email: "",
|
||||
mob_no: "",
|
||||
active: true,
|
||||
usrGrpId: "",
|
||||
});
|
||||
setIsEditing(false);
|
||||
}
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleModalSave = (data) => {
|
||||
setUsers((prevUsers) => [
|
||||
...prevUsers,
|
||||
{ ...data, id: prevUsers.length + 1 },
|
||||
]);
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
const handleUpdateSave = () => {
|
||||
setIsUpdateModalOpen(false);
|
||||
// Form input handler
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setUserData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ field: "userId", headerName: "User ID", width: 200 },
|
||||
{ field: "username", headerName: "Username", width: 200 },
|
||||
{ field: "fullName", headerName: "Full Name", width: 200 },
|
||||
{ field: "email", headerName: "Email", width: 200 },
|
||||
{ field: "usrGrpName", headerName: "User Group", width: 150 },
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 100,
|
||||
renderCell: ({ row }) => (
|
||||
<div>
|
||||
<div
|
||||
className="three-dots"
|
||||
onClick={() => handleThreeDotsClick(row.userId)}
|
||||
>
|
||||
<BsThreeDotsVertical />
|
||||
</div>
|
||||
{selectedUser === row.userId && (
|
||||
<div className="popover">
|
||||
<button onClick={() => handleDelete(row.userId)}>Delete</button>
|
||||
<button onClick={() => handleUpdate(row)}>Update</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
// Form submission handler
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
if (isEditing) {
|
||||
await updateUser(userData);
|
||||
toast.success("User updated successfully");
|
||||
} else {
|
||||
await createUser(userData);
|
||||
toast.success("User added successfully");
|
||||
}
|
||||
// Refresh user list
|
||||
const response = await getAllUsers();
|
||||
setUsers(response.data || []);
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Error saving user:", error);
|
||||
toast.error("There was an error while submitting the USER.");
|
||||
}
|
||||
};
|
||||
|
||||
// Delete user handler
|
||||
const handleDelete = async (userId) => {
|
||||
try {
|
||||
await deleteUser(userId);
|
||||
setUsers(users.filter((user) => user.userId !== userId));
|
||||
toast.success("User deleted successfully");
|
||||
} catch (error) {
|
||||
console.error("Error deleting user:", error);
|
||||
toast.error("Failed to delete user");
|
||||
}
|
||||
};
|
||||
|
||||
// Excel export handler
|
||||
const exportToExcel = () => {
|
||||
const worksheet = XLSX.utils.json_to_sheet(users);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "UserDetails");
|
||||
XLSX.writeFile(workbook, "UserMaintenance.xlsx");
|
||||
};
|
||||
|
||||
// Excel import handlers
|
||||
const handleFileChange = (e) => {
|
||||
// Implement your file import logic here
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
// Implement your import logic here
|
||||
toast.success("File imported successfully");
|
||||
setIsImportModalOpen(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box
|
||||
sx={{ height: "calc(100vh - 150px)", width: "100%", overflowX: "auto" }}
|
||||
>
|
||||
<div className="text-center text-3xl text-white bg-gray-400">
|
||||
User Maintenance
|
||||
</div>
|
||||
<DataGrid
|
||||
className="bg-gray-400"
|
||||
rows={users}
|
||||
columns={columns}
|
||||
components={{
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleThreeDotsClick={handleThreeDotsClick}
|
||||
handleModal={handleModal}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
}}
|
||||
/>
|
||||
{isModalOpen && (
|
||||
<Modal
|
||||
setNewUser={setNewUser}
|
||||
newUser={newUser}
|
||||
onSave={handleModalSave}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{isUpdateModalOpen && (
|
||||
<UpdateModal
|
||||
user={selectedUserForUpdate}
|
||||
onUpdate={handleUpdateSave}
|
||||
onClose={() => setIsUpdateModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="80vh">
|
||||
<CircularProgress />
|
||||
</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 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,39 +1,95 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Sidebar from "./sidebar";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, Link, Routes, Route, Outlet } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Drawer,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Collapse,
|
||||
useTheme,
|
||||
styled,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Avatar,
|
||||
Typography,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ExpandMore,
|
||||
ExpandLess,
|
||||
Description as DescriptionIcon,
|
||||
Error as ErrorIcon,
|
||||
Storage as StorageIcon,
|
||||
SwapHoriz as SwapHorizIcon,
|
||||
Home as HomeIcon,
|
||||
Settings as SettingsIcon,
|
||||
GridView as GridViewIcon,
|
||||
BarChart as BarChartIcon,
|
||||
Apps as AppsIcon,
|
||||
Person as PersonIcon,
|
||||
Language as LanguageIcon,
|
||||
LockReset as LockResetIcon,
|
||||
Logout as LogoutIcon,
|
||||
Info as InfoIcon,
|
||||
Menu as MenuIcon
|
||||
} from '@mui/icons-material';
|
||||
import UserMaintanceComponent from "./UserMaintance";
|
||||
import UserGroupMaintanceComponent from "./UserGroupMaintance/UserGroupMaintance";
|
||||
import MenuMaintanceComponent from "./MenuMaintance/MenuMaintance";
|
||||
import MenuAccessControlComponent from "./MenuAccessControl/MenuAccessControl";
|
||||
import SystemParametersComponent from "./SystemParameters/SystemParameters";
|
||||
// import AccessTypeComponent from "./AccessType/AccessType";
|
||||
import AccessTypeComponent from "./AccessType/AccessType";
|
||||
import ApiRegistery from "./ApiRegistery/ApiRegistery";
|
||||
import TokenRegistery from "./TokenRegistery/TokenRegistery";
|
||||
import HomePage from "./HomePage";
|
||||
import Setup from "./Setup.js";
|
||||
import Report from "./Report";
|
||||
import { FaCog, FaUsers, FaSignOutAlt, FaHome, FaChartBar } from "react-icons/fa";
|
||||
import Setup from "./Setup";
|
||||
import Report from "./reports/Report";
|
||||
import SequenceGenerator from "./document sequence/sequencegenerator";
|
||||
import About from "./dropdown/about";
|
||||
import Profile from "./dropdown/profile";
|
||||
import DashboardRunnerAll from "./../dashboardnew/dashboardrunner/dashboardrunnerall";
|
||||
import DashboardNewAll from "../dashboardnew/dashboardbuildernewall";
|
||||
import DashboardNewAdd from "../dashboardnew/dashboardadd/dashboardbuilderadd";
|
||||
import DashboardNewEdit from "../dashboardnew/editdashboard/editformdashboard";
|
||||
import EditNewDash from "../dashboardnew/editdashboard/editdashboard";
|
||||
import DashboardRunner from "../dashboardnew/dashboardrunner/dashboardrunner";
|
||||
import SubMenuMaintenance from "./sub menu/submenumaintanence";
|
||||
|
||||
|
||||
const Dashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [menus, setMenus] = useState([]);
|
||||
const [selectedUserMaintance, setSelectedUserMaintance] = useState(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
|
||||
const [openTransaction, setOpenTransaction] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [submenuAnchorEl, setSubmenuAnchorEl] = useState(null);
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
|
||||
const drawerWidth = 220;
|
||||
const collapsedWidth = 60;
|
||||
|
||||
useEffect(() => {
|
||||
const user = JSON.parse(localStorage.getItem("user"));
|
||||
setCurrentUser(user);
|
||||
|
||||
const fetchMenusData = async () => {
|
||||
const token = localStorage.getItem("authtoken");
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
const apiUrl = `${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`;
|
||||
console.log("Fetching menus from API:", apiUrl);
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
@@ -58,142 +114,490 @@ const Dashboard = () => {
|
||||
fetchMenusData();
|
||||
}, [navigate]);
|
||||
|
||||
const handleMenuItemClick = (menuItem) => {
|
||||
setSelectedUserMaintance(menuItem);
|
||||
setContent(menuItem.menuItemDesc); // Update content based on clicked menu item
|
||||
};
|
||||
|
||||
const handleHomeClick = () => {
|
||||
setSelectedUserMaintance(null);
|
||||
setContent("Home");
|
||||
navigate('/dashboard');
|
||||
};
|
||||
|
||||
const handleSetupClick = () => {
|
||||
setSelectedUserMaintance(null);
|
||||
setContent("Setup");
|
||||
navigate('/dashboard/setup');
|
||||
};
|
||||
|
||||
const handleReportClick = () => {
|
||||
setSelectedUserMaintance(null);
|
||||
setContent("Report");
|
||||
navigate('/dashboard/reports');
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (menu) => {
|
||||
const routeMap = {
|
||||
"User Maintance": "/dashboard/setup/user-maintenance",
|
||||
"User Group Maintance": "/dashboard/setup/user-group-maintenance",
|
||||
"Menu Maintance": "/dashboard/setup/menu-maintenance",
|
||||
"Menu Access Control": "/dashboard/setup/menu-access-control",
|
||||
"System Parameters": "/dashboard/setup/system-parameters",
|
||||
"Access Type": "/dashboard/setup/access-type",
|
||||
"Api Registery": "/dashboard/setup/api-registry",
|
||||
"Token Registery": "/dashboard/setup/token-registry",
|
||||
"sequence generator": "/dashboard/setup/document-sequence",
|
||||
};
|
||||
|
||||
if (routeMap[menu.menuItemDesc]) {
|
||||
navigate(routeMap[menu.menuItemDesc]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSidebarToggle = () => {
|
||||
setSidebarCollapsed(!sidebarCollapsed);
|
||||
};
|
||||
|
||||
const handleTransactionToggle = () => {
|
||||
setOpenTransaction(!openTransaction);
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleSubmenuOpen = (event) => {
|
||||
setSubmenuAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleSubmenuClose = () => {
|
||||
setSubmenuAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("authToken");
|
||||
localStorage.removeItem("user");
|
||||
navigate("/");
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleLanguageSelect = (lang) => {
|
||||
localStorage.setItem("appLanguage", lang);
|
||||
handleSubmenuClose();
|
||||
handleMenuClose();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const toggleMobileSidebar = () => {
|
||||
setMobileSidebarOpen(!mobileSidebarOpen);
|
||||
};
|
||||
|
||||
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-gradient-to-br from-purple-50 via-white to-blue-50">
|
||||
{/* Top Navigation Bar */}
|
||||
<div className="flex justify-between items-center bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-4 shadow-lg">
|
||||
<div className="flex items-center space-x-4">
|
||||
<h3 className="text-2xl font-bold">Dashboard</h3>
|
||||
<div className="h-6 w-px bg-white/30"></div>
|
||||
<nav className="flex space-x-6">
|
||||
<button
|
||||
onClick={handleHomeClick}
|
||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
||||
<div className="flex flex-col h-screen">
|
||||
{/* Navbar - fixed at the top */}
|
||||
<div className="h-16 flex-shrink-0 bg-gray-800 text-white p-4 z-10">
|
||||
<div className="flex justify-between items-center h-full">
|
||||
<div className="flex items-center">
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={toggleMobileSidebar}
|
||||
sx={{ display: { xs: 'block', lg: 'none' }, mr: 2 }}
|
||||
>
|
||||
<FaHome className="w-5 h-5" />
|
||||
<span>Home</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSetupClick}
|
||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
||||
>
|
||||
<FaCog className="w-5 h-5" />
|
||||
<span>Setup</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleReportClick}
|
||||
className="flex items-center space-x-2 hover:text-purple-200 transition-colors"
|
||||
>
|
||||
<FaChartBar className="w-5 h-5" />
|
||||
<span>Reports</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center space-x-2 bg-white/10 hover:bg-white/20 text-white font-semibold py-2 px-4 rounded-lg transition-all duration-200"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<FaSignOutAlt className="w-5 h-5" />
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Dashboard
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation Icons */}
|
||||
<Box sx={{ display: { xs: 'none', lg: 'flex' }, gap: 2 }}>
|
||||
<Tooltip title="Home">
|
||||
<IconButton color="inherit" onClick={handleHomeClick}>
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Settings">
|
||||
<IconButton color="inherit" onClick={handleSetupClick}>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Dashboard">
|
||||
<IconButton color="inherit" onClick={() => navigate('/dashboard/dashboard-runner-all')}>
|
||||
<GridViewIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Reports">
|
||||
<IconButton color="inherit" onClick={handleReportClick}>
|
||||
<BarChartIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* User Dropdown */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Tooltip title="Account settings">
|
||||
<IconButton
|
||||
onClick={handleMenuOpen}
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
aria-controls={anchorEl ? 'account-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={anchorEl ? 'true' : undefined}
|
||||
>
|
||||
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main' }}>
|
||||
<PersonIcon fontSize="small" />
|
||||
</Avatar>
|
||||
<Typography variant="body1" sx={{ ml: 1, color: 'white', display: { xs: 'none', lg: 'block' } }}>
|
||||
{currentUser ? currentUser.fullname || currentUser.username || currentUser.email : "Guest"}
|
||||
</Typography>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* User Menu */}
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
id="account-menu"
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
onClick={handleMenuClose}
|
||||
PaperProps={{
|
||||
elevation: 0,
|
||||
sx: {
|
||||
overflow: 'visible',
|
||||
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
|
||||
mt: 1.5,
|
||||
'& .MuiAvatar-root': {
|
||||
width: 32,
|
||||
height: 32,
|
||||
ml: -0.5,
|
||||
mr: 1,
|
||||
},
|
||||
'&:before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 14,
|
||||
width: 10,
|
||||
height: 10,
|
||||
bgcolor: 'background.paper',
|
||||
transform: 'translateY(-50%) rotate(45deg)',
|
||||
zIndex: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
>
|
||||
<MenuItem onClick={() => navigate('/dashboard/about')}>
|
||||
<ListItemIcon>
|
||||
<InfoIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
About
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => navigate('/dashboard/profile')}>
|
||||
<ListItemIcon>
|
||||
<PersonIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
My Profile
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => navigate('/dashboard/dashboard-runner-all')}>
|
||||
<ListItemIcon>
|
||||
<GridViewIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Activity
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleSubmenuOpen}
|
||||
onMouseEnter={handleSubmenuOpen}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<LanguageIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Choose Language
|
||||
{Boolean(submenuAnchorEl) ? <ExpandLess sx={{ ml: 1 }} /> : <ExpandMore sx={{ ml: 1 }} />}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => navigate('/admin/resetpassword')}>
|
||||
<ListItemIcon>
|
||||
<LockResetIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Reset Password
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout}>
|
||||
<ListItemIcon>
|
||||
<LogoutIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Logout
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Language Submenu */}
|
||||
<Menu
|
||||
anchorEl={submenuAnchorEl}
|
||||
open={Boolean(submenuAnchorEl)}
|
||||
onClose={handleSubmenuClose}
|
||||
MenuListProps={{
|
||||
onMouseLeave: handleSubmenuClose,
|
||||
'aria-labelledby': 'language-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={() => handleLanguageSelect("en")}>English</MenuItem>
|
||||
<MenuItem onClick={() => handleLanguageSelect("hi")}>Hindi</MenuItem>
|
||||
<MenuItem onClick={() => handleLanguageSelect("ta")}>Tamil</MenuItem>
|
||||
<MenuItem onClick={() => handleLanguageSelect("te")}>Telugu</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Sidebar */}
|
||||
<Drawer
|
||||
variant="temporary"
|
||||
open={mobileSidebarOpen}
|
||||
onClose={toggleMobileSidebar}
|
||||
ModalProps={{
|
||||
keepMounted: true,
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: 'block', lg: 'none' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: 240 },
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<List>
|
||||
<ListItemButton onClick={() => { handleHomeClick(); toggleMobileSidebar(); }}>
|
||||
<ListItemIcon><HomeIcon /></ListItemIcon>
|
||||
<ListItemText primary="Home" />
|
||||
</ListItemButton>
|
||||
<ListItemButton onClick={() => { handleSetupClick(); toggleMobileSidebar(); }}>
|
||||
<ListItemIcon><SettingsIcon /></ListItemIcon>
|
||||
<ListItemText primary="Settings" />
|
||||
</ListItemButton>
|
||||
<ListItemButton onClick={() => { navigate('/dashboard/dashboard-runner-all'); toggleMobileSidebar(); }}>
|
||||
<ListItemIcon><GridViewIcon /></ListItemIcon>
|
||||
<ListItemText primary="Dashboard" />
|
||||
</ListItemButton>
|
||||
<ListItemButton onClick={() => { handleReportClick(); toggleMobileSidebar(); }}>
|
||||
<ListItemIcon><BarChartIcon /></ListItemIcon>
|
||||
<ListItemText primary="Reports" />
|
||||
</ListItemButton>
|
||||
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
{/* Main content area below navbar */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`bg-gradient-to-b from-purple-700 to-indigo-800 text-white transition-all duration-300 ${
|
||||
sidebarCollapsed ? "w-16" : "w-64"
|
||||
} min-w-16 shadow-xl`}
|
||||
{/* Integrated Sidebar */}
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: 'none', lg: 'block' },
|
||||
width: sidebarCollapsed ? collapsedWidth : drawerWidth,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: sidebarCollapsed ? collapsedWidth : drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
borderRight: '1px solid rgba(0, 0, 0, 0.12)',
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
height: 'calc(100vh - 64px)',
|
||||
position: 'relative',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-end p-2">
|
||||
<button
|
||||
onClick={handleSidebarToggle}
|
||||
className="text-white/80 hover:text-white bg-white/10 hover:bg-white/20 p-2 rounded-lg transition-all duration-200"
|
||||
<DrawerHeader>
|
||||
<IconButton onClick={handleSidebarToggle} size="small">
|
||||
{sidebarCollapsed ? <ChevronRight /> : <ChevronLeft />}
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<List>
|
||||
{/* Transaction with submenus */}
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton
|
||||
onClick={handleTransactionToggle}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
justifyContent: sidebarCollapsed ? 'center' : 'initial',
|
||||
px: 2.5,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{sidebarCollapsed ? ">>" : "<<"}
|
||||
</button>
|
||||
</div>
|
||||
<Sidebar
|
||||
menus={menus}
|
||||
handleMenuItemClick={handleMenuItemClick}
|
||||
collapsed={sidebarCollapsed}
|
||||
setCollapsed={setSidebarCollapsed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 p-6 overflow-auto bg-gradient-to-br from-purple-50 via-white to-blue-50">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{content === "Setup" ? (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<Setup />
|
||||
</div>
|
||||
) : content === "Home" ? (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<HomePage />
|
||||
</div>
|
||||
) : content === "Report" ? (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<Report />
|
||||
</div>
|
||||
) : selectedUserMaintance ? (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<h3 className="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b border-purple-100">
|
||||
{selectedUserMaintance.menuItemDesc}
|
||||
</h3>
|
||||
{selectedUserMaintance.menuItemDesc === "User Maintance" ? (
|
||||
<UserMaintanceComponent />
|
||||
) : selectedUserMaintance.menuItemDesc === "User Group Maintance" ? (
|
||||
<UserGroupMaintanceComponent />
|
||||
) : selectedUserMaintance.menuItemDesc === "Menu Maintance" ? (
|
||||
<MenuMaintanceComponent />
|
||||
) : selectedUserMaintance.menuItemDesc === "Menu Access Control" ? (
|
||||
<MenuAccessControlComponent />
|
||||
) : selectedUserMaintance.menuItemDesc === "System Parameters" ? (
|
||||
<SystemParametersComponent />
|
||||
) : selectedUserMaintance.menuItemDesc === "Api Registery" ? (
|
||||
<ApiRegistery />
|
||||
) : selectedUserMaintance.menuItemDesc === "Token Registery" ? (
|
||||
<TokenRegistery />
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<HomePage />
|
||||
</div>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: sidebarCollapsed ? 'auto' : 3,
|
||||
justifyContent: 'center',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
<SwapHorizIcon />
|
||||
</ListItemIcon>
|
||||
{!sidebarCollapsed && (
|
||||
<>
|
||||
<ListItemText primary="Transaction" />
|
||||
{openTransaction ? <ExpandLess /> : <ExpandMore />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
<ListItemButton
|
||||
sx={{ pl: 4 }}
|
||||
onClick={() => navigate('/admin/regform')}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||
<DescriptionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Regform" />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={{ pl: 4 }}
|
||||
onClick={() => navigate('/admin/error404')}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||
<ErrorIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Additional container" />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
)}
|
||||
|
||||
{/* Masters (single item) */}
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton
|
||||
onClick={() => navigate('/admin/masters')}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
justifyContent: sidebarCollapsed ? 'center' : 'initial',
|
||||
px: 2.5,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: sidebarCollapsed ? 'auto' : 3,
|
||||
justifyContent: 'center',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
<StorageIcon />
|
||||
</ListItemIcon>
|
||||
{!sidebarCollapsed && (
|
||||
<ListItemText primary="Masters" />
|
||||
)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
{/* Dynamic menu items from API */}
|
||||
{menus.map((menu) => (
|
||||
<ListItem key={menu.menuItemId} disablePadding>
|
||||
<ListItemButton
|
||||
onClick={() => handleMenuItemClick(menu)}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
justifyContent: sidebarCollapsed ? 'center' : 'initial',
|
||||
px: 2.5,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: sidebarCollapsed ? 'auto' : 3,
|
||||
justifyContent: 'center',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
<StorageIcon />
|
||||
</ListItemIcon>
|
||||
{!sidebarCollapsed && (
|
||||
<ListItemText primary={menu.menuItemDesc} />
|
||||
)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Drawer>
|
||||
|
||||
{/* Content area */}
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: {
|
||||
xs: '100%',
|
||||
lg: `calc(100% - ${sidebarCollapsed ? collapsedWidth : drawerWidth}px)`
|
||||
},
|
||||
marginLeft: {
|
||||
xs: 0,
|
||||
lg: `${sidebarCollapsed ? collapsedWidth : drawerWidth}px`
|
||||
},
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
height: 'calc(100vh - 64px)',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
<Route index element={<HomePage />} />
|
||||
<Route path="setup" element={<Setup />}>
|
||||
<Route index element={<div>Select a setup option from the menu</div>} />
|
||||
<Route path="user-maintenance" element={<UserMaintanceComponent />} />
|
||||
<Route path="user-group-maintenance" element={<UserGroupMaintanceComponent />} />
|
||||
<Route path="menu-maintenance" element={<MenuMaintanceComponent />} />
|
||||
<Route path="menu-access-control" element={<MenuAccessControlComponent />} />
|
||||
<Route path="system-parameters" element={<SystemParametersComponent />} />
|
||||
<Route path="access-type" element={<AccessTypeComponent />} />
|
||||
<Route path="api-registry" element={<ApiRegistery />} />
|
||||
<Route path="token-registry" element={<TokenRegistery />} />
|
||||
<Route path="document-sequence" element={<SequenceGenerator />} />
|
||||
<Route path="sub-menu-maintenance/:menuItemId" element={<SubMenuMaintenance/>} />
|
||||
</Route>
|
||||
<Route path="reports" element={<Report />} />
|
||||
<Route path="about" element={<About/>} />
|
||||
<Route path="profile" element={<Profile/>} />
|
||||
<Route path="dashboard-runner-all" element={<DashboardRunnerAll/>}/>
|
||||
<Route path="dashboard-new-all" element={<DashboardNewAll/>}/>
|
||||
<Route path="dashboard-new-add" element={<DashboardNewAdd/>}/>
|
||||
<Route path="dashboard-new-edit/:id" element={<DashboardNewEdit/>}/>
|
||||
<Route path="edit-new-dash/:id" element={<EditNewDash/>}/>
|
||||
<Route path="dashrunner/:id" element={ <DashboardRunner/>}/>
|
||||
|
||||
<Route path="*" element={<div>Page Not Found</div>} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
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) {
|
||||
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 {
|
||||
background-color: #555;
|
||||
}
|
||||
/* For the main content adjustment */
|
||||
.main-content {
|
||||
margin-left: 220px;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.main-content.sidebar-collapsed {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
/* Active menu item style */
|
||||
.Mui-selected {
|
||||
background-color: #5e72e4 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.Mui-selected:hover {
|
||||
background-color: #233dd2 !important;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
|
||||
@@ -1,118 +1,192 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FiChevronRight, FiChevronDown, FiSettings } from "react-icons/fi";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FaChevronRight as FaChevronRightIcon } from "react-icons/fa";
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Drawer,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
styled,
|
||||
Collapse,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ExpandMore,
|
||||
ExpandLess,
|
||||
Description as DescriptionIcon,
|
||||
Error as ErrorIcon,
|
||||
Storage as StorageIcon,
|
||||
SwapHoriz as SwapHorizIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const Sidebar = ({ menus, handleMenuItemClick, collapsed }) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [openSubmenu, setOpenSubmenu] = useState(null);
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const drawerWidth = 220;
|
||||
const collapsedWidth = 60;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMenuItems = async () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.REACT_APP_API_BASE_URL}/fndMenu/menuloadbyuser`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = await res.json();
|
||||
setMenuItems(data);
|
||||
console.log("Fetched Menu Data:", data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching menu items:", error);
|
||||
const Sidebar = ({ onSidebarToggle }) => {
|
||||
const theme = useTheme();
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [openTransaction, setOpenTransaction] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleToggle = () => {
|
||||
setCollapsed(!collapsed);
|
||||
if (onSidebarToggle) {
|
||||
onSidebarToggle(!collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMenuItems();
|
||||
}, []);
|
||||
|
||||
const handleToggle = () => setIsOpen(!isOpen);
|
||||
const handleSubmenuToggle = (menu) => {
|
||||
setOpenSubmenu(openSubmenu === menu ? null : menu);
|
||||
const handleTransactionToggle = () => {
|
||||
setOpenTransaction(!openTransaction);
|
||||
};
|
||||
|
||||
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col h-screen bg-gray-100 text-black ${isOpen ? "w-64" : "w-20"
|
||||
} transition-all duration-300`}
|
||||
<>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
width: collapsed ? collapsedWidth : drawerWidth,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: collapsed ? collapsedWidth : drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
backgroundColor: theme.palette.grey[200], // Consistent with theme
|
||||
borderRight: '1px solid rgba(0, 0, 0, 0.12)',
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
height: '100vh',
|
||||
position: 'relative'
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
{isOpen ? (
|
||||
<span className="text-lg font-bold">Menu</span>
|
||||
) : (
|
||||
<FiSettings
|
||||
className="text-2xl cursor-pointer"
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
)}
|
||||
<button onClick={handleToggle}>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<DrawerHeader>
|
||||
<IconButton onClick={handleToggle} size="small">
|
||||
{collapsed ? <ChevronRight /> : <ChevronLeft />}
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<List>
|
||||
{/* Transaction with submenus */}
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton
|
||||
onClick={handleTransactionToggle}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
justifyContent: collapsed ? 'center' : 'initial',
|
||||
px: 2.5,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 6h16M4 12h16m-7 6h7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<nav className="flex flex-col mt-4">
|
||||
{menuItems.map((item) => (
|
||||
<div key={item.id} className="relative">
|
||||
<button
|
||||
className="flex items-center p-4 hover:bg-gray-300 w-full text-left"
|
||||
onClick={() => handleSubmenuToggle(item.id)}
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: collapsed ? 'auto' : 3,
|
||||
justifyContent: 'center',
|
||||
color: 'inherit'
|
||||
}}
|
||||
>
|
||||
{item.icon && <item.icon className="w-6 h-6 mr-2" />}
|
||||
<span>{item.menuItemDesc}</span>
|
||||
<SwapHorizIcon />
|
||||
</ListItemIcon>
|
||||
{!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>
|
||||
{/*
|
||||
<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>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
{subItem.subMenus && (
|
||||
<div className="ml-auto">
|
||||
{openSubmenu === subItem.id ? (
|
||||
<FiChevronDown />
|
||||
) : (
|
||||
<FiChevronRight />
|
||||
{!collapsed && (
|
||||
<Collapse in={openTransaction} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||
<ListItemButton
|
||||
sx={{ pl: 4 }}
|
||||
onClick={() => navigate('/admin/regform')}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||
<DescriptionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Regform" />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
sx={{ pl: 4 }}
|
||||
onClick={() => navigate('/admin/error404')}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 0, mr: 3, justifyContent: 'center' }}>
|
||||
<ErrorIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Additional container" />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
)}
|
||||
</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>
|
||||
</Link>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Drawer>
|
||||
|
||||
{/* Main content spacer */}
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: `calc(100% - ${collapsed ? collapsedWidth : drawerWidth}px)`,
|
||||
marginLeft: `${collapsed ? collapsedWidth : drawerWidth}px`,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{/* Your main content goes here */}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
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 { Eye, EyeOff, User, Lock, Mail, Camera, UserPlus, ArrowLeft, Check } from 'lucide-react';
|
||||
|
||||
// Mock navigation function for demo
|
||||
const mockNavigate = (path) => {
|
||||
console.log(`Navigating to: ${path}`);
|
||||
alert(`Would navigate to: ${path}`);
|
||||
};
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountCircle, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import './Login.css'; // Import CSS file for custom styling
|
||||
|
||||
const CreateAccountPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [reEnterPassword, setReEnterPassword] = useState('');
|
||||
const [avatarImage, setAvatarImage] = useState(null);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showReEnterPassword, setShowReEnterPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [passwordStrength, setPasswordStrength] = useState(0);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Calculate password strength
|
||||
let strength = 0;
|
||||
if (password.length >= 8) strength++;
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
||||
setPasswordStrength(strength);
|
||||
}, [password]);
|
||||
|
||||
const handleCreateAccount = async (e) => {
|
||||
const handleCreateAccount = (e) => {
|
||||
e.preventDefault();
|
||||
setErrorMessage('');
|
||||
setIsLoading(true);
|
||||
|
||||
if (!email || !password || !reEnterPassword) {
|
||||
setErrorMessage('All fields are required.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== reEnterPassword) {
|
||||
setErrorMessage('Passwords do not match.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
setErrorMessage('Password must be at least 8 characters long.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
console.log('Account created successfully!');
|
||||
alert('Account created successfully!');
|
||||
mockNavigate('/login');
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
// Your create account logic here
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-purple-100 relative overflow-hidden">
|
||||
{/* Background Elements */}
|
||||
<div className="absolute inset-0">
|
||||
{/* Subtle geometric shapes */}
|
||||
<div className="absolute top-20 left-20 w-32 h-32 bg-purple-200 rounded-full opacity-20 animate-pulse"></div>
|
||||
<div className="absolute bottom-20 right-20 w-24 h-24 bg-purple-300 rounded-full opacity-20 animate-pulse" style={{ animationDelay: '1s' }}></div>
|
||||
<div className="absolute top-1/2 right-10 w-16 h-16 bg-purple-400 rounded-full opacity-15 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
||||
|
||||
{/* Grid pattern */}
|
||||
<div className="absolute inset-0 opacity-5" style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%236B46C1' fill-opacity='0.4'%3E%3Cpath d='M20 20h20v20H20V20zm-20 0h20v20H0V20z'/%3E%3C/g%3E%3C/svg%3E")`
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
||||
{/* Background Logo */}
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
||||
|
||||
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
|
||||
CLOUDNSURE
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create Account Card */}
|
||||
<div className={`relative w-full max-w-lg transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||
{/* Card Background */}
|
||||
<div className="bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="bg-gradient-to-r from-purple-600 to-purple-700 p-8 text-center relative">
|
||||
<div className="absolute top-4 left-4">
|
||||
<button
|
||||
onClick={() => mockNavigate('/login')}
|
||||
className="p-2 text-white hover:bg-white hover:bg-opacity-20 rounded-xl transition-all duration-200"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div 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 className="space-y-4">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-white bg-opacity-20 rounded-2xl">
|
||||
<UserPlus className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Create Account</h1>
|
||||
<p className="text-purple-100">Join our community today</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className="p-8 space-y-6">
|
||||
{/* Error Message */}
|
||||
{errorMessage && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Avatar Upload */}
|
||||
<div className="text-center">
|
||||
<div className="relative inline-block">
|
||||
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Create Account</h2>
|
||||
<form onSubmit={handleCreateAccount} className="space-y-4">
|
||||
<div className="flex items-center justify-center">
|
||||
<input
|
||||
accept="image/*"
|
||||
id="avatar-input"
|
||||
@@ -161,171 +47,61 @@ const CreateAccountPage = () => {
|
||||
className="hidden"
|
||||
onChange={handleAvatarChange}
|
||||
/>
|
||||
<label htmlFor="avatar-input" className="cursor-pointer group">
|
||||
<div className="relative">
|
||||
<label htmlFor="avatar-input" className="cursor-pointer">
|
||||
<img
|
||||
alt="Avatar"
|
||||
src={avatarImage || 'https://via.placeholder.com/120/E5E7EB/6B7280?text=Photo'}
|
||||
className="w-28 h-28 rounded-full border-4 border-purple-100 group-hover:border-purple-300 transition-all duration-200 object-cover"
|
||||
src={avatarImage || 'https://via.placeholder.com/120'}
|
||||
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>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-2">Click to upload your photo</p>
|
||||
</div>
|
||||
|
||||
{/* Form Fields */}
|
||||
<div className="space-y-5">
|
||||
{/* Email Field */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Email Address</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<Mail className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-gray-600">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
||||
placeholder="Enter your email"
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Password Field */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Password</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-gray-600">Password</label>
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full pl-12 pr-12 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
||||
placeholder="Create a strong password"
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
/>
|
||||
<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>
|
||||
<label className="block text-gray-600">Re-enter Password</label>
|
||||
<input
|
||||
type={showReEnterPassword ? 'text' : 'password'}
|
||||
type="password"
|
||||
value={reEnterPassword}
|
||||
onChange={(e) => setReEnterPassword(e.target.value)}
|
||||
className={`w-full pl-12 pr-12 py-4 bg-gray-50 border rounded-xl focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 ${
|
||||
reEnterPassword && password !== reEnterPassword
|
||||
? 'border-red-300 focus:ring-red-500'
|
||||
: reEnterPassword && password === reEnterPassword
|
||||
? 'border-green-300 focus:ring-green-500'
|
||||
: 'border-gray-200 focus:ring-purple-500'
|
||||
}`}
|
||||
placeholder="Confirm your password"
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowReEnterPassword(!showReEnterPassword)}
|
||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
type="submit"
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300 transition duration-300 ease-in-out"
|
||||
>
|
||||
{showReEnterPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
{reEnterPassword && password === reEnterPassword && (
|
||||
<div className="absolute right-12 top-1/2 transform -translate-y-1/2">
|
||||
<Check className="w-5 h-5 text-green-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{reEnterPassword && password !== reEnterPassword && (
|
||||
<p className="text-xs text-red-500">Passwords do not match</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create Account Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateAccount}
|
||||
disabled={isLoading || !email || !password || !reEnterPassword || password !== reEnterPassword}
|
||||
className="w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-purple-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none disabled:cursor-not-allowed"
|
||||
>
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
<span>Creating Account...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UserPlus className="w-5 h-5" />
|
||||
<span>Create Account</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Login Link */}
|
||||
<div className="text-center pt-4 border-t border-gray-100">
|
||||
<p className="text-gray-600 mb-3">Already have an account?</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => mockNavigate('/login')}
|
||||
className="inline-flex items-center space-x-2 text-purple-600 hover:text-purple-700 font-semibold transition-colors"
|
||||
>
|
||||
<User className="w-4 h-4" />
|
||||
<span>Sign In</span>
|
||||
Create Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="mt-4 text-center">
|
||||
<p className="text-gray-600">
|
||||
Already have an account?{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate('/login')}
|
||||
className="text-blue-600 hover:underline focus:outline-none"
|
||||
>
|
||||
Log in
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,191 +1,96 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Mail, ArrowLeft, Shield, CheckCircle, AlertCircle, Sparkles } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountCircle } from '@mui/icons-material';
|
||||
import './Login.css'; // Import CSS file for custom styling
|
||||
|
||||
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
|
||||
const API_FORGOT_PASSWORD = `${API_BASE_URL}/backend/api/resources/forgotpassword`;
|
||||
|
||||
const ForgotPasswordPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [messageType, setMessageType] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [particles, setParticles] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
const newParticles = Array.from({ length: 35 }, (_, i) => ({
|
||||
id: i,
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
size: Math.random() * 3 + 1,
|
||||
duration: Math.random() * 4 + 3,
|
||||
delay: Math.random() * 2,
|
||||
}));
|
||||
setParticles(newParticles);
|
||||
}, []);
|
||||
const [messageType, setMessageType] = useState(''); // State to manage message type for styling
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setMessage('');
|
||||
setMessageType('');
|
||||
|
||||
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.');
|
||||
setMessageType('success');
|
||||
setEmail('');
|
||||
} else {
|
||||
setMessage('Please enter a valid email address.');
|
||||
setMessageType('error');
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage(`Error during reset password: ${error.message}`);
|
||||
setMessageType('error');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
console.error('Error during reset password:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackToLogin = () => {
|
||||
alert('Back to login functionality would be implemented here');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
|
||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
||||
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
|
||||
|
||||
{particles.map((particle) => (
|
||||
<div
|
||||
key={particle.id}
|
||||
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
|
||||
style={{
|
||||
left: `${particle.x}%`,
|
||||
top: `${particle.y}%`,
|
||||
width: `${particle.size}px`,
|
||||
height: `${particle.size}px`,
|
||||
animationDuration: `${particle.duration}s`,
|
||||
animationDelay: `${particle.delay}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="absolute inset-0 opacity-20" style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
||||
<div className="flex justify-center mt-4 space-x-2">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
|
||||
))}
|
||||
<div className="relative min-h-screen flex items-center justify-center bg-gray-800">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center text-gray-200 text-9xl font-bold opacity-10">
|
||||
CLOUDNSURE
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10">
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<AccountCircle className="text-7xl text-gray-700" />
|
||||
</div>
|
||||
|
||||
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
|
||||
|
||||
<div className="relative p-8 space-y-6">
|
||||
<button
|
||||
onClick={handleBackToLogin}
|
||||
className="group absolute top-6 left-6 p-2 text-gray-400 hover:text-purple-600 transition-colors rounded-full hover:bg-purple-50"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
||||
</button>
|
||||
|
||||
<div className="text-center space-y-4 pt-8">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
|
||||
<Shield className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4">Forgot Password</h2>
|
||||
<p className="text-center text-gray-600 mb-4">
|
||||
Enter your email address and we'll send you a link to reset your password.
|
||||
</p>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">Forgot Password?</h1>
|
||||
<p className="text-gray-600 leading-relaxed">Don't worry! Enter your email address and we'll send you a secure link to reset your password.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<div className={`rounded-xl p-4 text-sm animate-pulse flex items-center space-x-3 ${
|
||||
messageType === 'error'
|
||||
? 'bg-red-50 border border-red-200 text-red-700'
|
||||
: 'bg-green-50 border border-green-200 text-green-700'
|
||||
}`}>
|
||||
{messageType === 'error' ? (
|
||||
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
||||
) : (
|
||||
<CheckCircle className="w-5 h-5 flex-shrink-0" />
|
||||
)}
|
||||
<span>{message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Email Address</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<Mail className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<label className="block text-gray-600">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
||||
placeholder="Enter your email address"
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
|
||||
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"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||
<div className="relative flex items-center justify-center space-x-2">
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
<span>Sending Reset Link...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Mail className="w-5 h-5" />
|
||||
<span>Send Reset Link</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
Reset Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center pt-4 border-t border-gray-200">
|
||||
<p className="text-gray-600 mb-3">Remember your password?</p>
|
||||
</form>
|
||||
{message && (
|
||||
<div className={`mt-4 text-center ${messageType === 'error' ? 'text-red-500' : 'text-green-500'}`}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
<p className="mt-4 text-center text-gray-600">
|
||||
Remember your password?{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBackToLogin}
|
||||
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
|
||||
onClick={() => navigate('/login')}
|
||||
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" />
|
||||
<span>Back to Sign In</span>
|
||||
Log in
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,55 +1,61 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Eye, EyeOff, User, Lock, Mail, Sparkles, Shield, ArrowRight } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountCircle, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import './Login.css'; // Import CSS file for custom styling
|
||||
|
||||
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;
|
||||
const API_TOKEN_SESSION = `${API_BASE_URL}/token/session`;
|
||||
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [particles, setParticles] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
const newParticles = Array.from({ length: 40 }, (_, i) => ({
|
||||
id: i,
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
size: Math.random() * 3 + 1,
|
||||
duration: Math.random() * 4 + 3,
|
||||
delay: Math.random() * 2,
|
||||
}));
|
||||
setParticles(newParticles);
|
||||
}, []);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
setErrorMessage('');
|
||||
setIsLoading(true);
|
||||
|
||||
if (!username || !password) {
|
||||
setErrorMessage('Username and password are required.');
|
||||
setIsLoading(false);
|
||||
if (!email || !password) {
|
||||
setErrorMessage('Email and password are required.');
|
||||
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') ||
|
||||
(storedCredentials[username] && storedCredentials[username] === password)) {
|
||||
localStorage.setItem('authToken', 'dummy-token');
|
||||
localStorage.setItem('currentUser', username);
|
||||
navigate('/Dashboard');
|
||||
} else {
|
||||
setErrorMessage('Invalid username or password. Please use sysadmin/test3 or your registered credentials.');
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Login failed:', errorText);
|
||||
setErrorMessage('Login failed. Please check your credentials.');
|
||||
return;
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
@@ -61,174 +67,86 @@ const LoginPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-purple-50">
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/4 w-80 h-80 bg-purple-200 rounded-full mix-blend-multiply filter blur-xl opacity-40 animate-pulse"></div>
|
||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
||||
<div className="absolute bottom-1/4 left-1/3 w-72 h-72 bg-indigo-200 rounded-full mix-blend-multiply filter blur-xl opacity-35 animate-pulse" style={{ animationDelay: '4s' }}></div>
|
||||
|
||||
{particles.map((particle) => (
|
||||
<div
|
||||
key={particle.id}
|
||||
className="absolute bg-purple-400 rounded-full opacity-20 animate-bounce"
|
||||
style={{
|
||||
left: `${particle.x}%`,
|
||||
top: `${particle.y}%`,
|
||||
width: `${particle.size}px`,
|
||||
height: `${particle.size}px`,
|
||||
animationDuration: `${particle.duration}s`,
|
||||
animationDelay: `${particle.delay}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="absolute inset-0 opacity-20" style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%239333ea' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
||||
<div className="flex justify-center mt-4 space-x-2">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Sparkles key={i} className="w-8 h-8 text-purple-300 opacity-30 animate-pulse" style={{ animationDelay: `${i * 0.3}s` }} />
|
||||
))}
|
||||
<div className="relative min-h-screen flex items-center justify-center bg-gray-700">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center text-gray-100 text-9xl font-bold opacity-75">
|
||||
CLOUDNSURE
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full max-w-md bg-white shadow-md rounded-lg p-6 z-10 ">
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<AccountCircle className="text-7xl text-gray-700" />
|
||||
</div>
|
||||
|
||||
<div className={`relative w-full max-w-md transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||
<div className="relative bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600 opacity-5 rounded-3xl"></div>
|
||||
|
||||
<div className="relative p-8 space-y-6">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl shadow-lg transform hover:rotate-12 transition-transform duration-300">
|
||||
<Shield className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold text-center text-gray-800 mb-4 ">Log in</h2>
|
||||
{errorMessage && <div className="mb-4 text-red-600">{errorMessage}</div>}
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">Welcome Back</h1>
|
||||
<p className="text-gray-600">Sign in to your account</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm animate-pulse">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Username</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<User className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<label className="block text-gray-600">Email</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
||||
placeholder="Enter your username"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Password</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-400 to-indigo-500 rounded-xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
||||
<div className="relative bg-gray-50 border-2 border-gray-200 rounded-xl overflow-hidden focus-within:border-purple-500 focus-within:bg-white transition-all">
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<Lock className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-gray-600">Password</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full pl-12 pr-12 py-4 bg-transparent text-gray-800 placeholder-gray-500 focus:outline-none"
|
||||
placeholder="Enter your password"
|
||||
className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring focus:ring-opacity-50"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center space-x-3 cursor-pointer group">
|
||||
<div className="relative">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
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'}`}>
|
||||
{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>
|
||||
<span className="ml-2 text-gray-600">Remember Me</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
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?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogin}
|
||||
disabled={isLoading}
|
||||
className="group relative w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl hover:from-purple-700 hover:to-indigo-700 transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none overflow-hidden"
|
||||
type="submit"
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-300"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500 to-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||
<div className="relative flex items-center justify-center space-x-2">
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
<span>Signing in...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Sign In</span>
|
||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center pt-4 border-t border-gray-200">
|
||||
<p className="text-gray-600 mb-3">Don't have an account?</p>
|
||||
</form>
|
||||
<div className="mt-4 text-center">
|
||||
<p className="text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateAccount}
|
||||
className="group inline-flex items-center space-x-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-indigo-600 font-semibold hover:from-purple-700 hover:to-indigo-700 transition-all"
|
||||
className="text-blue-600 hover:underline focus:outline-none"
|
||||
>
|
||||
<User className="w-4 h-4 text-purple-600 group-hover:text-purple-700" />
|
||||
<span>Create New Account</span>
|
||||
Create Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</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',
|
||||
monospace;
|
||||
}
|
||||
|
||||
|
||||
|
||||
16
src/index.js
16
src/index.js
@@ -1,22 +1,12 @@
|
||||
// index.js
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
const theme = createTheme({
|
||||
// your theme configuration
|
||||
});
|
||||
|
||||
import { createRoot } from 'react-dom/client';
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
|
||||
root.render(
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider theme={theme}>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
root.render(<App />);
|
||||
@@ -1,19 +1,23 @@
|
||||
|
||||
|
||||
const TOKEN_KEY = 'authToken';
|
||||
const TOKEN_KEY = 'CurrentUser';
|
||||
|
||||
|
||||
export const setToken = (token) => {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
export const setCurrentUser = (user) => {
|
||||
localStorage.setItem(TOKEN_KEY, user);
|
||||
};
|
||||
|
||||
export const getCurrentUser = () => {
|
||||
return JSON.parse(localStorage.getItem(TOKEN_KEY));
|
||||
};
|
||||
|
||||
export const getToken = () => {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
const userData = localStorage.getItem(TOKEN_KEY);
|
||||
return userData ? JSON.parse(userData).token : null;
|
||||
};
|
||||
|
||||
|
||||
export const removeToken = () => {
|
||||
export const removeCurrentUser = () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
@@ -21,3 +25,7 @@ export const removeToken = () => {
|
||||
export const isTokenAvailable = () => {
|
||||
return !!localStorage.getItem(TOKEN_KEY); // Returns true if token exists
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
233
src/variables/chart.js
Normal file
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