소스 검색

no message

master
부모
커밋
008edeb6e6
7개의 변경된 파일548개의 추가작업 그리고 78개의 파일을 삭제
  1. +9
    -0
      src/menu-items/setting.js
  2. +181
    -0
      src/pages/lionerUserActionLog/UserActionLogSearchForm.js
  3. +77
    -0
      src/pages/lionerUserActionLog/UserActionLogTable.js
  4. +94
    -0
      src/pages/lionerUserActionLog/index.js
  5. +175
    -78
      src/pages/pdf/PdfSearchPage/PdfTable.js
  6. +11
    -0
      src/routes/SettingRoutes.js
  7. +1
    -0
      src/utils/ApiPathConst.js

+ 9
- 0
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',


+ 181
- 0
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 (
<MainCard xs={12} md={12} lg={12}
content={false}
sx={{width:CARD_MAX_WIDTH}}
>

<form onSubmit={handleSubmit(onSubmit)}>
{/*row 1*/}
<Grid sx={{mb:2}}/>

{/*row 2*/}
<Grid container alignItems={"center"} >
<Grid item xs={9} s={6} md={5} lg={3} sx={{ ml:3, mr:3, }}>
<InputLabel>
Username
</InputLabel>
<TextField
fullWidth
{...register("username")}
inputProps={{maxLength: 32}}
id='username'
autoComplete="off"
/>
</Grid>

<Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3,}}>
<InputLabel>
Action Description
</InputLabel>
<TextField
fullWidth
{...register("actionDesc")}
inputProps={{maxLength: 45}}
id='actionDesc'
autoComplete="off"
/>
</Grid>

<Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, }}>
<InputLabel>
Date Period
</InputLabel>
<Grid container>
<Grid item xs={5.5} s={5.5} md={5.5} lg={5.5}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DemoItem components={['DatePicker']}>
<DatePicker
id="fromDate"
format="DD/MM/YYYY"
onError={(newError) => setFromError(newError)}
slotProps={{
field: { clearable: true },
textField: {
helperText: fromErrorMessage,
},
}}
value={fromDate === null ? null : dayjs(fromDate)}
onChange={(newValue) => setFromDate(newValue)}
/>
</DemoItem >
</LocalizationProvider>
</Grid>

<Grid item xs={1} s={1} md={1} lg={1} sx={{display: 'flex', justifyContent:"center", alignItems: 'center'}}>
To
</Grid>

<Grid item xs={5.5} s={5.5} md={5.5} lg={5.5}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DemoItem components={['DatePicker']}>
<DatePicker
id="toDate"
format="DD/MM/YYYY"
onError={(newError) => setToError(newError)}
slotProps={{
field: { clearable: true },
textField: {
helperText: toErrorMessage,
},
}}
//label="To Date"
value={toDate === null ? null : dayjs(toDate)}
onChange={(newValue) => setToDate(newValue)}
/>
</DemoItem >
</LocalizationProvider>
</Grid>
</Grid>
</Grid>
</Grid>


{/*last row*/}
<Grid container maxWidth justifyContent="flex-start" sx={{mt:2,mb:2}}>
<ThemeProvider theme={LIONER_BUTTON_THEME}>
<Grid item sx={{ml:3, mr:1.5, }}>
<Button
variant="contained"
type="submit"
color="save"
disabled={fromError || toError}
>
Search
</Button>
</Grid>

<Grid item sx={{ml:{xs:3, mb:1.5, lg:1.5}, mr:3, }}>
<Button
variant="contained"
onClick={resetForm}
color="cancel">
Clear
</Button>
</Grid>

</ThemeProvider>
</Grid>
</form>
</MainCard>
);
};

export default UserActionLogSearchForm;

+ 77
- 0
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) => (
<div>
{getDateString(params.row.created, 'dd/MM/yyyy HH:mm:ss')}
</div>
),
},

];

return (
<div style={{/*height: '65vh',*/ width: '100%'}}>
<DataGrid
rows={rows}
columns={columns}
columnHeaderHeight={45}
editMode="row"
getRowHeight={() => '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}
/>

</div>
);
}

+ 94
- 0
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 ?
<LoadingComponent/>
:
<Grid container rowSpacing={3} columnSpacing={2.75}>
<ThemeProvider theme={LIONER_FORM_THEME}>
<Grid item xs={12} md={12} lg={12} sx={{mt:-1}}>
<Grid container justifyContent="flex-start" alignItems="center">
<Grid item xs={3} sx={{mb: -2.25}} >
<Box sx={{ display: 'flex', alignItems: 'center'}}>
<Typography align="center" variant="h4" >Login Log</Typography>
</Box>
</Grid>
</Grid>
</Grid>

{/*row 1*/}
<Grid item xs={12} md={12} lg={12}>
<UserActionLogSearchForm applySearch={applySearch}/>
</Grid>
{/*row 2*/}
<Grid item xs={12} md={12} lg={12} sx={{mt:{lg:-1.5}}}>
<MainCard elevation={0}
content={false}
sx={{width:CARD_MAX_WIDTH}}
>
<UserActionLogTable
recordList={record}
/>
</MainCard>
</Grid>
</ThemeProvider>
</Grid>
);
};

export default UserActionLogSearchPanel;

+ 175
- 78
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
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
: <UploadFileIcon sx={{fontSize: 25}}/>; // 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
/>
</ThemeProvider>
];

// 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
? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
: <UploadFileIcon sx={{fontSize: 25}}/>; // Upload icon if not uploaded

const upload2Label = isUploaded2 ? "Update 2" : "Upload 2";
// >>> END CHANGES HERE <<<

actions.push(
<ThemeProvider key="UploadSign2" theme={LIONER_BUTTON_THEME}>
<GridActionsCellItem
icon={<UploadFileIcon sx={{fontSize: 25}}/>}
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"
/>
</ThemeProvider>
@@ -292,7 +392,7 @@ export default function PdfTable({recordList}) {
icon={<FileDownloadIcon sx={{fontSize: 25}}/>}
label="Download"
className="textPrimary"
onClick={handleDownloadClick(id, "{params.row.templateName}")}
onClick={handleDownloadClick(id)}
color="download"
/>
</ThemeProvider>
@@ -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 */}
<Dialog
open={isDialogOpen}
onClose={handleCloseDialog}
fullWidth
<Dialog
open={isDialogOpen}
onClose={handleCloseDialog}
fullWidth
maxWidth="sm"
// Prevent closing when upload is active
disableEscapeKeyDown={isUploading}
disableEscapeKeyDown={isUploading}
TransitionProps={{ onExited: handleCloseDialog }}
>
<DialogTitle>
Upload Signature Page (1-Page PDF)
{/* Dynamic Title based on currentUploadRow state */}
**{getUploadDialogTitle(currentUploadRow.formCode, currentUploadRow.refType)}**
</DialogTitle>
<DialogContent dividers>
<Typography gutterBottom>
Please select the single-page PDF file containing the signature for record ID: **{currentRowId}**.
</Typography>
<Box sx={{ mt: 2, textAlign: 'center' }}>
{/* Button to trigger file selection */}
<Button
@@ -394,11 +491,11 @@ export default function PdfTable({recordList}) {
ref={fileInputRef}
type="file"
accept="application/pdf"
onChange={handleFileChange}
onChange={handleFileChange}
style={{ display: 'none' }}
disabled={isUploading}
/>
{/* Display the selected file name if a file is chosen */}
{fileInputRef.current?.files[0] && (
<Typography variant="body2" sx={{ mt: 1 }}>
@@ -406,7 +503,7 @@ export default function PdfTable({recordList}) {
</Typography>
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog} color="inherit" disabled={isUploading}>


+ 11
- 0
src/routes/SettingRoutes.js 파일 보기

@@ -24,6 +24,7 @@ const PromotionChannelPage = Loadable(lazy(() => import('pages/lionerPromotionCh
const TagPage = Loadable(lazy(() => import('pages/lionerTagPage')));
const AuditLogPage = Loadable(lazy(() => import('pages/lionerAuditTrailPage')));
const LoginLogPage = Loadable(lazy(() => import('pages/lionerLoginLog')));
const UserActionLogPage = Loadable(lazy(() => import('pages/lionerUserActionLog')));
const EmailConfigPage = Loadable(lazy(() => import('pages/lionerEmailConfig')));
const GenerateReminderPage = Loadable(lazy(() => import('pages/lionerManualButtonPage')));
const ClientDepartmentPage = Loadable(lazy(() => import('pages/lionerClientDepartmentPage')));
@@ -177,6 +178,16 @@ const SettingRoutes = () => {
)
),
},
{
path: 'userActionLog',
element: (
handleRouteAbility(
ability.can('VIEW', 'USER'),
<UserActionLogPage />,
<Navigate to="/" />
)
),
},
{
path: 'loginLog',
element: (


+ 1
- 0
src/utils/ApiPathConst.js 파일 보기

@@ -46,6 +46,7 @@ export const GET_TAG_COMBO_LIST = "/tag/combo"
export const UPDATE_TAG_PATH = "/tag/save"
export const GET_AUDIT_LOG_LIST = "/auditLog"
export const GET_USER_LOGIN_LOG_LIST = "/user/loginLog"
export const GET_USER_ACTION_LOG_LIST = "/userActionLog"
export const GET_APPLICATION_PATH = "/application"
export const GET_APPLICATION_COMBO_PATH = "/application/combo"
export const POST_APPLICATION_PATH = "/application/save"


불러오는 중...
취소
저장