diff --git a/src/menu-items/setting.js b/src/menu-items/setting.js
index 8432005..9d6bbff 100644
--- a/src/menu-items/setting.js
+++ b/src/menu-items/setting.js
@@ -132,6 +132,15 @@ const setting = {
breadcrumbs: false,
ability:['VIEW','LOGIN_LOG']
},
+ {
+ id: 'userActionLog',
+ title: 'Action Log',
+ type: 'item',
+ url: '/userActionLog',
+ icon: icons.MenuUnfoldOutlined,
+ breadcrumbs: false,
+ ability:['VIEW','USER']
+ },
// {
// id: 'passwordPolicy',
// title: 'Password Policy',
diff --git a/src/pages/lionerUserActionLog/UserActionLogSearchForm.js b/src/pages/lionerUserActionLog/UserActionLogSearchForm.js
new file mode 100644
index 0000000..063d033
--- /dev/null
+++ b/src/pages/lionerUserActionLog/UserActionLogSearchForm.js
@@ -0,0 +1,181 @@
+// material-ui
+import {
+ Button,
+ Grid, TextField,InputLabel
+} from '@mui/material';
+import MainCard from "../../components/MainCard";
+import {useForm} from "react-hook-form";
+import * as React from "react";
+import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs";
+import {DemoItem} from "@mui/x-date-pickers/internals/demo";
+import {DatePicker} from "@mui/x-date-pickers/DatePicker";
+import dayjs from "dayjs";
+import {useState} from "react";
+import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider";
+import {LIONER_BUTTON_THEME} from "../../themes/colorConst";
+import {ThemeProvider} from "@emotion/react";
+import {CARD_MAX_WIDTH} from "../../themes/themeConst";
+// ==============================|| DASHBOARD - DEFAULT ||============================== //
+
+const UserActionLogSearchForm = ({applySearch}) => {
+
+ const { reset, register, handleSubmit } = useForm()
+ const [fromDate, setFromDate] = useState(null);
+ const [toDate, setToDate] = useState(null);
+ const [fromError, setFromError] = React.useState(null);
+ const [toError, setToError] = React.useState(null);
+ const fromErrorMessage = React.useMemo(() => {
+ switch (fromError) {
+ case 'invalidDate': {
+ return "Invalid date";
+ }
+ }
+ }, [fromError]);
+
+ const toErrorMessage = React.useMemo(() => {
+ switch (toError) {
+ case 'invalidDate': {
+ return "Invalid date";
+ }
+ }
+ }, [toError]);
+
+ const onSubmit = (data) => {
+ const temp = {
+ username: data.username,
+ ipAddr: data.ipAddr,
+ fromDate: fromDate === null ? null : dayjs(fromDate).format('YYYY-MM-DD'),
+ toDate: toDate === null ? null : dayjs(toDate).format('YYYY-MM-DD'),
+ }
+ applySearch(temp);
+ };
+
+ function resetForm(){
+ setFromDate(null);
+ setToDate(null);
+ reset();
+ }
+
+ return (
+
+
+
+
+ );
+};
+
+export default UserActionLogSearchForm;
diff --git a/src/pages/lionerUserActionLog/UserActionLogTable.js b/src/pages/lionerUserActionLog/UserActionLogTable.js
new file mode 100644
index 0000000..ef271d5
--- /dev/null
+++ b/src/pages/lionerUserActionLog/UserActionLogTable.js
@@ -0,0 +1,77 @@
+// material-ui
+import * as React from 'react';
+import {
+ DataGrid,
+} from "@mui/x-data-grid";
+import {useEffect} from "react";
+import {CustomNoRowsOverlay, dateComparator, getDateString} from "../../utils/CommonFunction";
+
+export default function UserActionLogTable({recordList}) {
+ const [rows, setRows] = React.useState([]);
+
+ const [paginationModel, setPaginationModel] = React.useState({
+ page: 0,
+ pageSize:10
+ });
+
+ useEffect(() => {
+ setPaginationModel({page:0,pageSize:10});
+ setRows(recordList);
+ }, [recordList]);
+
+ const columns = [
+ {
+ id: 'userName',
+ field: 'username',
+ headerName: 'Username',
+ flex: 1,
+ },
+ {
+ id: 'actionDesc',
+ field: 'actionDesc',
+ headerName: 'Action Description',
+ flex: 3,
+ },
+ {
+ id: 'created',
+ field: 'created',
+ headerName: 'Action Time',
+ flex: 1,
+ sortComparator: dateComparator,
+ renderCell: (params) => (
+
+ {getDateString(params.row.created, 'dd/MM/yyyy HH:mm:ss')}
+
+ ),
+ },
+
+ ];
+
+ return (
+
+ 'auto'}
+ initialState={{
+ pagination: {
+ paginationModel: {page: 0, pageSize: 15},
+ },
+ }}
+ paginationModel={paginationModel}
+ onPaginationModelChange={setPaginationModel}
+ slots={{
+ noRowsOverlay: () => (
+ CustomNoRowsOverlay()
+ )
+ }}
+ pageSizeOptions={[15]}
+ autoHeight
+ getRowId={(row) => row.username + row.created + row.refId + row.refType}
+ />
+
+
+ );
+}
diff --git a/src/pages/lionerUserActionLog/index.js b/src/pages/lionerUserActionLog/index.js
new file mode 100644
index 0000000..07e234a
--- /dev/null
+++ b/src/pages/lionerUserActionLog/index.js
@@ -0,0 +1,94 @@
+// material-ui
+import {
+ Box,
+ Grid, Typography
+} from '@mui/material';
+import MainCard from "../../components/MainCard";
+import UserActionLogSearchForm from "./UserActionLogSearchForm";
+import UserActionLogTable from "./UserActionLogTable";
+import {useEffect, useState} from "react";
+import * as React from "react";
+import axios from "axios";
+import {apiPath} from "../../auth/utils";
+import {GET_USER_ACTION_LOG_LIST} from "../../utils/ApiPathConst";
+import LoadingComponent from "../extra-pages/LoadingComponent";
+
+import {LIONER_FORM_THEME, CARD_MAX_WIDTH} from "../../themes/themeConst";
+import {ThemeProvider} from "@emotion/react";
+
+// ==============================|| DASHBOARD - DEFAULT ||============================== //
+
+const UserActionLogSearchPanel = () => {
+
+ const [record, setRecord] = useState([]);
+ const [searchCriteria, setSearchCriteria] = useState({});
+ const [onReady, setOnReady] = useState(false);
+
+ useEffect(() => {
+ getGroupList();
+ }, []);
+
+ useEffect(() => {
+ setOnReady(true);
+ }, [record]);
+
+ useEffect(() => {
+ getGroupList();
+ }, [searchCriteria]);
+
+ function getGroupList() {
+ axios.get(`${apiPath}${GET_USER_ACTION_LOG_LIST}`,
+ {params: searchCriteria}
+ )
+ .then((response) => {
+ if (response.status === 200) {
+ setRecord(response.data.records);
+ }
+ })
+ .catch(error => {
+ console.log(error);
+ return false;
+ });
+ }
+
+ function applySearch(input) {
+ setSearchCriteria(input);
+ }
+
+ return (
+ !onReady ?
+
+ :
+
+
+
+
+
+
+ Login Log
+
+
+
+
+
+ {/*row 1*/}
+
+
+
+ {/*row 2*/}
+
+
+
+
+
+
+
+ );
+};
+
+export default UserActionLogSearchPanel;
diff --git a/src/pages/pdf/PdfSearchPage/PdfTable.js b/src/pages/pdf/PdfSearchPage/PdfTable.js
index 36191aa..739dada 100644
--- a/src/pages/pdf/PdfSearchPage/PdfTable.js
+++ b/src/pages/pdf/PdfSearchPage/PdfTable.js
@@ -9,36 +9,44 @@ import {
} from "@mui/x-data-grid";
import EditIcon from '@mui/icons-material/Edit';
import UploadFileIcon from '@mui/icons-material/UploadFile';
-import CheckCircleIcon from '@mui/icons-material/CheckCircle';
+import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import {
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
Typography,
- Box, // for centering and layout
- CircularProgress, // Import for loading indicator
+ Box,
+ CircularProgress,
} from '@mui/material';
import {useContext, useEffect} from "react";
import {useNavigate} from "react-router-dom";
// Note: Assuming these utility functions/components are defined elsewhere
-import {CustomNoRowsOverlay, dateComparator, getDateString} from "../../../utils/CommonFunction";
+import {CustomNoRowsOverlay, dateComparator, getDateString} from "../../../utils/CommonFunction";
import AbilityContext from "../../../components/AbilityProvider";
import {LIONER_BUTTON_THEME} from "../../../themes/colorConst";
import {ThemeProvider} from "@emotion/react";
// ==============================|| PDF TABLE ||============================== //
+// Define the structure for the row data stored in state
+const initialUploadState = {
+ id: null,
+ templateName: null, // This will be the formCode
+ refType: null, // To differentiate between upload1 and upload2 if needed
+}
+
export default function PdfTable({recordList}) {
const [rows, setRows] = React.useState(recordList);
const [rowModesModel] = React.useState({});
- // State for Dialog visibility, row ID, and Loading state
+ // State for Dialog visibility and Loading state
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
- const [currentRowId, setCurrentRowId] = React.useState(null);
- const [isUploading, setIsUploading] = React.useState(false);
+ // State to hold the ID, templateName, and refType for the current upload operation
+ const [currentUploadRow, setCurrentUploadRow] = React.useState(initialUploadState);
+ const [isUploading, setIsUploading] = React.useState(false);
const navigate = useNavigate()
const ability = useContext(AbilityContext);
@@ -47,7 +55,7 @@ export default function PdfTable({recordList}) {
page: 0,
pageSize:10
});
-
+
// Ref for the hidden file input
const fileInputRef = React.useRef(null);
@@ -60,21 +68,36 @@ export default function PdfTable({recordList}) {
navigate(`/pdf/maintain/${id}`);
};
- // Opens the upload dialog and sets the current row ID
- const handleUploadClick = (id) => () => {
- setCurrentRowId(id);
+ /**
+ * Opens the upload dialog and sets the current row details for Upload 1
+ */
+ const handleUploadClick = (id, templateName, formCode) => () => {
+ setCurrentUploadRow({
+ id: id,
+ templateName: templateName,
+ formCode: formCode,
+ refType: "upload1"
+ });
setIsDialogOpen(true);
};
- const handleUpload2Click = (id, templateName) => () => {
+ /**
+ * Opens the upload dialog and sets the current row details for Upload 2
+ */
+ const handleUpload2Click = (id, templateName, formCode) => () => {
// Placeholder for Upload 2
console.log(`Uploading for row ID ${id} (Upload 2)`);
- setCurrentRowId(id);
+ setCurrentUploadRow({
+ id: id,
+ templateName: templateName,
+ formCode: formCode,
+ refType: "upload2" // A different refType for a different upload logic/API
+ });
setIsDialogOpen(true);
};
const handleDownloadClick = (id) => () => {
-
+
// 1. Construct the download URL with the ID query parameter
const downloadUrl = `${apiPath}/pdf/download-ff/${id}`;
@@ -83,7 +106,7 @@ export default function PdfTable({recordList}) {
responseType: 'blob', // IMPORTANT: Tells axios to handle the response as binary data
})
.then((response) => {
-
+
// 2. Extract Filename from Content-Disposition Header
const contentDisposition = response.headers['content-disposition'];
let filename = `document-${id}.pdf`; // Fallback filename
@@ -102,12 +125,12 @@ export default function PdfTable({recordList}) {
const blob = new Blob([response.data], { type: 'application/pdf' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
-
+
link.href = url;
- link.setAttribute('download', filename);
+ link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
-
+
// 4. Clean up
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
@@ -121,17 +144,68 @@ export default function PdfTable({recordList}) {
const handleCloseDialog = () => {
setIsDialogOpen(false);
- setCurrentRowId(null);
+ setCurrentUploadRow(initialUploadState); // Reset the current row
if (fileInputRef.current) {
fileInputRef.current.value = ""; // Clear the file input
}
};
+
+ // Function to generate the dynamic dialog title
+ const getUploadDialogTitle = (formCode, refType) => {
+ console.log("formCode:" + formCode + " refType:" + refType);
+ if (refType === 'upload1') {
+ switch (formCode) {
+ case "IDA":
+ return "Upload Page 12";
+ case "FNA":
+ return "Upload Page 7";
+ case "HSBCFIN":
+ return "Upload Page 11";
+ case "HSBCA31":
+ return "Upload Page 28-29";
+ case "MLB03S":
+ return "Upload Page 7";
+ case "MLFNA_EN":
+ return "Upload Page 4";
+ case "MLFNA_CHI":
+ return "Upload Page 4";
+ case "SLGII":
+ return "Upload Page 13";
+ case "SLAPP":
+ return "Upload Page 16";
+ case "SLFNA_EN":
+ return "Upload Page 5";
+ case "SLFNA_CHI":
+ return "Upload Page 5";
+ default:
+ return "Unknown Form";
+ }
+ }else if (refType === 'upload2') {
+ switch (formCode) {
+ case "MLB03S":
+ return "Upload Page 12";
+ case "SLGII":
+ return "Upload Page 15-16";
+ case "SLAPP":
+ return "Upload Page 18-19";
+ default:
+ return "Unknown Form";
+ }
+ }
+ // Handle other refTypes if needed, e.g., 'upload2'
+ if (refType === 'upload2') {
+ return `Upload Template 2 for ${formCode}`;
+ }
+
+ return "Upload File"; // Fallback
+ };
// Function to handle file selection and API submission
const handleFileChange = async (event) => {
const file = event.target.files[0];
-
- if (!file) return;
+ const { id, templateName, refType } = currentUploadRow;
+
+ if (!file || !id || !refType) return;
if (file.type !== "application/pdf") {
alert("Please select a PDF file.");
@@ -139,43 +213,58 @@ export default function PdfTable({recordList}) {
return;
}
- const uploadUrl = apiPath + '/pdf/upload1';
+ // The URL should potentially change based on refType if upload2 uses a different endpoint
+ const uploadUrl = `${apiPath}${POST_SIG_UPLOAD1}`; // Assuming POST_SIG_UPLOAD1 is used for both for now
// 1. Create FormData
const formData = new FormData();
formData.append('file', file);
- formData.append('refId', currentRowId);
- formData.append('refType', "upload1");
+ formData.append('refId', id);
+ formData.append('refType', refType); // Pass the refType to the backend
setIsUploading(true);
try {
- axios.post(`${apiPath}${POST_SIG_UPLOAD1}`,
+ const response = await axios.post(
+ uploadUrl,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
}
- )
- .then((response) => {
- if (response.status === 200) {
- // Optional: Read response if the server returns data
- alert('Upload success');
- console.log(`PDF file ${file.name} successfully uploaded for record ID: ${currentRowId}!`);
+ );
+
+ if (response.status === 200) {
+ alert('Upload success');
+ console.log(`PDF file ${file.name} successfully uploaded for record ID: ${id} with refType: ${refType}!`);
+
+ // --- START: Update local state to show the green tick ---
+ const uploadedFileId = response.data?.fileId || 'temp-id-' + Date.now(); // Assume the response has a fileId or use a temp one
+
+ setRows(prevRows =>
+ prevRows.map(row => {
+ if (row.id === id) {
+ // Update the relevant file ID field based on refType
+ const updateField = refType === 'upload1' ? 'upload1FileId' : 'upload2FileId';
+ return {
+ ...row,
+ [updateField]: uploadedFileId // Set the file ID to trigger the icon
+ };
+ }
+ return row;
+ })
+ );
+ // --- END: Update local state to show the green tick ---
+
+ } else {
+ throw new Error(`Upload failed with status: ${response.status}`);
+ }
- }
- setIsUploading(false);
- })
- .catch((error) => {
- setIsUploading(false);
- console.log(error);
- return false;
- });
-
-
} catch (error) {
console.error('Upload error:', error);
- alert(`Error uploading file: ${error.message}`);
+ // Check if the error has a response and use its message if available
+ const errorMessage = error.response?.data?.message || error.message;
+ alert(`Error uploading file: ${errorMessage}`);
} finally {
// 3. Cleanup and close
setIsUploading(false);
@@ -189,7 +278,7 @@ export default function PdfTable({recordList}) {
fileInputRef.current.click();
}
};
-
+
const columns = [
{
field: 'actions',
@@ -211,7 +300,7 @@ export default function PdfTable({recordList}) {
]
},
},
-
+
{
id: 'templateName',
field: 'templateName',
@@ -237,16 +326,19 @@ export default function PdfTable({recordList}) {
width: 100,
cellClassName: 'actions',
getActions: ({ id, row }) => {
-
- // Check if a file ID exists to determine if a file is present
- const isUploaded = !!row.upload1FileId;
-
- // Determine the icon and label based on upload status
- const upload1Icon = isUploaded
+
+ // Check if a file ID exists to determine if a file is present for Upload 1
+ const isUploaded1 = !!row.upload1FileId;
+ const isUploaded2 = !!row.upload2FileId; // <<< ADD THIS LINE <<<
+ const templateName = row.templateName;
+ const formCode = row.formCode;
+
+ // Determine the icon and label based on upload status for Upload 1
+ const upload1Icon = isUploaded1
? // Green tick if uploaded
: ; // Upload icon if not uploaded
-
- const upload1Label = isUploaded ? "Update Signature" : "Upload Signature";
+
+ const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature";
// Define the actions
const actions = [
@@ -255,21 +347,29 @@ export default function PdfTable({recordList}) {
icon={upload1Icon} // Use the dynamic icon
label={upload1Label} // Use the dynamic label
className="textPrimary"
- onClick={handleUploadClick(id)}
+ onClick={handleUploadClick(id, templateName, formCode)} // Pass templateName here
color="upload" // Use 'upload' color which will apply to the button
/>
];
// Conditional rendering logic for Upload 2
- if (row.templateName === "MLB03S" || row.templateName === "SLGII" || row.templateName === "SLAPP") {
+ if (row.formCode === "MLB03S" || row.formCode === "SLGII" || row.formCode === "SLAPP") {
+ // Determine the icon and label based on upload status for Upload 2 <<< START CHANGES HERE <<<
+ const upload2Icon = isUploaded2
+ ? // Green tick if uploaded
+ : ; // Upload icon if not uploaded
+
+ const upload2Label = isUploaded2 ? "Update 2" : "Upload 2";
+ // >>> END CHANGES HERE <<<
+
actions.push(
}
- label="Upload2"
+ icon={upload2Icon} // <<< USE DYNAMIC ICON <<<
+ label={upload2Label} // <<< USE DYNAMIC LABEL <<<
className="textPrimary"
- onClick={handleUpload2Click(id, row.templateName)}
+ onClick={handleUpload2Click(id, templateName, formCode)} // Pass templateName here
color="upload"
/>
@@ -292,7 +392,7 @@ export default function PdfTable({recordList}) {
icon={}
label="Download"
className="textPrimary"
- onClick={handleDownloadClick(id, "{params.row.templateName}")}
+ onClick={handleDownloadClick(id)}
color="download"
/>
@@ -344,7 +444,7 @@ export default function PdfTable({recordList}) {
rows={rows}
columns={columns}
// Increased height to accommodate the multi-line header
- columnHeaderHeight={70}
+ columnHeaderHeight={70}
editMode="row"
rowModesModel={rowModesModel}
getRowHeight={() => 'auto'}
@@ -358,25 +458,22 @@ export default function PdfTable({recordList}) {
pageSizeOptions={[10]}
autoHeight
/>
-
+
{/* The Upload Dialog Box */}
-