@@ -105,6 +105,15 @@ const setting = { | |||
breadcrumbs: false, | |||
ability:['VIEW','USER'] | |||
}, | |||
{ | |||
id: 'consultant', | |||
title: 'Consultant', | |||
type: 'item', | |||
url: '/consultant', | |||
icon: icons.UserOutlined, | |||
breadcrumbs: false, | |||
ability:['VIEW','USER'] | |||
}, | |||
// { | |||
// id: 'auditLog', | |||
// title: 'Audit Log', | |||
@@ -0,0 +1,122 @@ | |||
// material-ui | |||
import * as React from 'react'; | |||
import { | |||
DataGrid, | |||
GridActionsCellItem, | |||
} from "@mui/x-data-grid"; | |||
import EditIcon from '@mui/icons-material/Edit'; | |||
import {useContext, useEffect, useRef} from "react"; | |||
import {useNavigate} from "react-router-dom"; | |||
import {CustomNoRowsOverlay} from "../../../utils/CommonFunction"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
// ==============================|| EVENT TABLE ||============================== // | |||
export default function ApplicationTable({recordList}) { | |||
const [rows, setRows] = React.useState(recordList); | |||
const [rowModesModel] = React.useState({}); | |||
const ability = useContext(AbilityContext); | |||
const navigate = useNavigate() | |||
const gridRef = useRef(null); | |||
useEffect(() => { | |||
setRows(recordList); | |||
}, [recordList]); | |||
const handleEditClick = (id) => () => { | |||
navigate('/application/maintain/'+ id); | |||
}; | |||
const columns = [ | |||
{ | |||
field: 'actions', | |||
type: 'actions', | |||
headerName: 'Actions', | |||
width: 100, | |||
cellClassName: 'actions', | |||
getActions: ({id}) => { | |||
return [ | |||
<GridActionsCellItem | |||
key="OutSave" | |||
icon={<EditIcon sx={{fontSize: 25}}/>} | |||
label="Edit" | |||
className="textPrimary" | |||
onClick={handleEditClick(id)} | |||
color="warning" | |||
disabled={!ability.can('VIEW','APPLICATION')} | |||
/>] | |||
}, | |||
}, | |||
{ | |||
id: 'applicationName', | |||
field: 'applicationName', | |||
headerName: 'Application', | |||
flex: 1, | |||
renderCell: (params) => { | |||
return ( | |||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
{params.value} | |||
</div> | |||
); | |||
} | |||
}, | |||
{ | |||
id: 'subDivisions', | |||
field: 'subDivisions', | |||
//type: 'date', | |||
//sortable: false, | |||
headerName: 'Sub-Divisions', | |||
flex: 1, | |||
renderCell: (params) => { | |||
return ( | |||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
{params.value} | |||
</div> | |||
); | |||
} | |||
}, | |||
{ | |||
id: 'tags', | |||
field: 'tags', | |||
//type: 'date', | |||
//sortable: false, | |||
headerName: 'Tags', | |||
flex: 0.8, | |||
}, | |||
{ | |||
id: 'responsibleOfficer', | |||
field: 'responsibleOfficer', | |||
headerName: 'Responsible Officer', | |||
flex: 0.7, | |||
}, | |||
]; | |||
return ( | |||
<div style={{/*height: 400,*/ width: '100%', }}> | |||
<DataGrid | |||
disableColumnMenu | |||
ref={gridRef} | |||
rows={rows} | |||
columns={columns} | |||
columnHeaderHeight={45} | |||
editMode="row" | |||
rowModesModel={rowModesModel} | |||
getRowHeight={() => 'auto'} | |||
initialState={{ | |||
pagination: { | |||
paginationModel: {page: 0, pageSize: 5}, | |||
}, | |||
}} | |||
slots={{ | |||
noRowsOverlay: () => ( | |||
CustomNoRowsOverlay() | |||
) | |||
}} | |||
pageSizeOptions={[5]} | |||
autoHeight | |||
/> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,363 @@ | |||
// material-ui | |||
import { | |||
Button, | |||
FormControlLabel, | |||
Grid, InputAdornment, Switch, TextField, Typography | |||
} from '@mui/material'; | |||
import MainCard from "../../../components/MainCard"; | |||
import * as React from "react"; | |||
import {useForm} from "react-hook-form"; | |||
import {useContext, useEffect, useState} from "react"; | |||
import LoadingComponent from "../../extra-pages/LoadingComponent"; | |||
import {useLocation, useNavigate, useParams} from "react-router-dom"; | |||
import { | |||
GeneralConfirmWindow, | |||
getComboValueByIdList, | |||
getComboValueByLabel, | |||
getDateString, | |||
getDeletedRecordWithRefList, | |||
getIdList, | |||
getNewRecordWithRefList, isOptionEqualToValue, isStringEmptyAfterTrim, notifyDeleteError, notifyDeleteSuccess, | |||
notifySaveSuccess, trimDataBeforePost, | |||
} from "../../../utils/CommonFunction"; | |||
import axios from "axios"; | |||
import {apiPath} from "../../../auth/utils"; | |||
import { | |||
CHECK_EVENT_DUPLICATE, //GET_EVENT_NOTIFICATION_OVERTIME, | |||
GET_CONSULTANT_PATH, | |||
GET_SUB_DIVISION_COMBO_LIST, | |||
POST_CONSULTANT_PATH | |||
} from "../../../utils/ApiPathConst"; | |||
import {LIONER_BUTTON_THEME, LIONER_LONG_BUTTON_THEME, GENERAL_RED_COLOR} from "../../../themes/colorConst"; | |||
import dayjs from "dayjs"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import UploadContext from "../../../components/UploadProvider"; | |||
import {isObjEmpty} from "../../../utils/Utils"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
import {CARD_MAX_WIDTH} from "../../../themes/themeConst"; | |||
const ConsultantForm = ({ refConsultantDetail, | |||
isNewRecord, | |||
getConsultantDetail, | |||
}) => { | |||
const location = useLocation(); | |||
const queryParams = new URLSearchParams(location.search); | |||
const id = queryParams.get("id"); | |||
const params = useParams(); | |||
const navigate = useNavigate(); | |||
const ability = useContext(AbilityContext); | |||
const [onReady, setOnReady] = useState(false); | |||
const [errors, setErrors] = useState({}); | |||
const [consultantDetail, setConsultantDetail] = useState({}); | |||
const [isCollectData, setIsCollectData] = useState(false); | |||
const [userConfirm, setUserConfirm] = useState(false); | |||
const [isEditing, setIsEditing] = useState(false); | |||
const { setIsUploading } = useContext(UploadContext); | |||
const [refConsultant, setRefConsultant] = useState({}); | |||
//form data | |||
const {register, getValues, setValue} = useForm(); | |||
const [isWindowOpen, setIsWindowOpen] = React.useState(false); | |||
const handleClose = () => { | |||
setIsWindowOpen(false); | |||
}; | |||
const handleDeleteClick = () => { | |||
setIsWindowOpen(true); | |||
}; | |||
function updateData(){ | |||
axios.delete(`${apiPath}${GET_CONSULTANT_PATH}/${params.id}`, | |||
) | |||
.then((response) => { | |||
if (response.status === 204) { | |||
notifyDeleteSuccess(); | |||
setIsWindowOpen(false); | |||
returnSearchPage(); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
// } | |||
} | |||
useEffect(()=>{ | |||
if (!isNewRecord) { | |||
setRefConsultant(refConsultantDetail); | |||
} | |||
},[]); | |||
useEffect(()=>{ | |||
//if ref data ready | |||
if (!isObjEmpty(refConsultant)) { | |||
//checkOvertime(); | |||
alert(id + " " + refConsultant.id); | |||
if(refConsultant.id !== null){ | |||
setValue("name", refConsultant.name); | |||
} | |||
setDob(dayjs(getDateString(refConsultant.dob))); | |||
} | |||
},[refConsultant]); | |||
useEffect(()=>{ | |||
if(!isObjEmpty(refConsultant)) { | |||
setOnReady(true); | |||
} | |||
else if(isNewRecord){ | |||
setOnReady(true); | |||
setIsEditing(true); | |||
} | |||
},[refConsultant]); | |||
useEffect(() => { | |||
//upload latest data to parent | |||
if(isCollectData){ | |||
const values = getValues(); | |||
const formErrors = {}; | |||
if (isStringEmptyAfterTrim(values.name)) { | |||
formErrors.name = 'Last Name is required'; | |||
} | |||
setErrors(formErrors); | |||
if (Object.keys(formErrors).length === 0) { | |||
let data = {}; | |||
data["name"] = values.name; | |||
//data["id"] = values.id; | |||
setConsultantDetail(data); | |||
} | |||
else if(isCollectData){ | |||
setUserConfirm(false); | |||
setIsCollectData(false); | |||
} | |||
// setUserConfirm(false); | |||
// setIsCollectData(false); | |||
} | |||
}, [isCollectData]); | |||
function returnSearchPage(){ | |||
navigate('/consultant'); | |||
} | |||
useEffect(() => { | |||
if (userConfirm) { | |||
postConsultant(); | |||
} | |||
setUserConfirm(false); | |||
}, [consultantDetail]); | |||
const submitData = () => { | |||
setIsCollectData(!isCollectData); | |||
setUserConfirm(true); | |||
} | |||
const updateIsEdit = () => { | |||
setIsEditing(!isEditing); | |||
} | |||
function postConsultant(){ | |||
setIsUploading(true); | |||
const temp = trimDataBeforePost(consultantDetail); | |||
axios.post(`${apiPath}${POST_CONSULTANT_PATH}`, | |||
temp, | |||
) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
notifySaveSuccess(); | |||
if(isNewRecord){ | |||
setIsUploading(false); | |||
navigate('/consultant') | |||
} | |||
else{ | |||
setIsUploading(false); | |||
getConsultantDetail(params.id); | |||
//checkOvertime(); | |||
setIsEditing(!isEditing); | |||
} | |||
setIsCollectData(!isCollectData); | |||
} | |||
}) | |||
.catch(error => { | |||
if (error.response.status === 422){ | |||
const formErrors = {}; | |||
formErrors.subDivision = error.response.data.error; | |||
setErrors(formErrors); | |||
} | |||
console.log(error); | |||
setIsUploading(false); | |||
return false; | |||
}); | |||
} | |||
return ( | |||
!onReady ? | |||
<LoadingComponent/> | |||
: | |||
<MainCard | |||
content={false} | |||
sx={{width:CARD_MAX_WIDTH}} | |||
> | |||
<form> | |||
<Grid container> | |||
<Grid item xs={12} s={12} md={12} lg={5.5} sx={{ml: 3, mr: 3, mb: 3, mt:1}}> | |||
<Grid container alignItems={"flex-start"}> | |||
<Grid item xs={4} s={4} md={4} lg={1.5} | |||
sx={{ml: 3, mr: 3, mt:1, display: 'flex', alignItems: 'flex-start'}}> | |||
<Typography variant="lionerSize" component="span"> | |||
Name: | |||
</Typography> | |||
</Grid> | |||
<Grid item xs={7} s={7} md={7} lg={7}> | |||
<TextField | |||
fullWidth | |||
{...register("name", | |||
{ | |||
value: refConsultant.name, | |||
})} | |||
id='name' | |||
required | |||
disabled={!isEditing} | |||
inputProps={{maxLength: 500, style: {fontSize: '1.1rem'}}} | |||
InputProps={{ | |||
style: { minHeight:'42.5px', maxHeight: '50vh' }, | |||
}} | |||
multiline | |||
maxRows={10} | |||
autoComplete="off" | |||
/> | |||
</Grid> | |||
</Grid> | |||
{/*row 2*/} | |||
{ | |||
(isEditing ) ? | |||
<Grid container maxWidth justifyContent="space-between"> | |||
<ThemeProvider theme={LIONER_BUTTON_THEME}> | |||
<Grid item> | |||
<Grid container> | |||
<Grid item sx={{ml:{xs:1.5,md:1.5,lg:1.5}, mr:1.5, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="save" | |||
onClick={submitData} | |||
> | |||
Save | |||
</Button> | |||
</Grid> | |||
<Grid item sx={{ml:1.5, mr:1.5, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="cancel" | |||
onClick={isNewRecord? returnSearchPage : updateIsEdit} | |||
> | |||
Cancel | |||
</Button> | |||
</Grid> | |||
<Grid item sx={{ml:{xs:1.5,md:1.5,lg:1.5}, mr:3, mb:1, mt:2}}> | |||
{!isNewRecord && (<Button | |||
variant="contained" | |||
color="delete" | |||
disabled={!ability.can('MAINTAIN','CONSULTANT')} | |||
onClick={handleDeleteClick} | |||
> | |||
Delete | |||
</Button> | |||
)} | |||
<GeneralConfirmWindow | |||
isWindowOpen={isWindowOpen} | |||
title={"Attention"} | |||
//content={`Confirm to delete Event "${consultantDetail.name}" ?`} | |||
content={`Are you sure to delete this consultant?`} | |||
onNormalClose={handleClose} | |||
onConfirmClose={updateData} | |||
/> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
<Grid item> | |||
<Grid container> | |||
<ThemeProvider theme={LIONER_LONG_BUTTON_THEME}> | |||
{/* <Grid item sx={{ml:{xs:1.5,md:1.5,lg:3}, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="saveAs" | |||
// onClick={copyEventAsNew} | |||
disabled={isNewRecord} | |||
> | |||
Copy as new | |||
</Button> | |||
</Grid> */} | |||
{/* <Grid item sx={{ml:{xs:1.5,md:1.5,lg:3}, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="create" | |||
onClick={createNewApplication} | |||
disabled={isNewRecord} | |||
> | |||
New Application | |||
</Button> | |||
</Grid> */} | |||
</ThemeProvider> | |||
</Grid> | |||
</Grid> | |||
</ThemeProvider> | |||
</Grid> | |||
: | |||
<Grid container maxWidth justifyContent="space-between" sx={{ml:2, mr:2}}> | |||
<ThemeProvider theme={LIONER_BUTTON_THEME}> | |||
<Grid item> | |||
<Grid container> | |||
<Grid item sx={{ml:1, mr:3, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="save" | |||
onClick={updateIsEdit} | |||
disabled={!ability.can('MAINTAIN','CONSULTANT')} | |||
> | |||
Edit | |||
</Button> | |||
</Grid> | |||
<Grid item sx={{ml:1, mr:3, mb:1, mt:2}}> | |||
<Button | |||
variant="contained" | |||
color="cancel" | |||
onClick={returnSearchPage} | |||
disabled={!ability.can('VIEW','CONSULTANT')} | |||
> | |||
Back | |||
</Button> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
<Grid item> | |||
</Grid> | |||
</ThemeProvider> | |||
</Grid> | |||
} | |||
</Grid> | |||
</Grid> | |||
</form> | |||
</MainCard> | |||
); | |||
}; | |||
export default ConsultantForm; |
@@ -0,0 +1,119 @@ | |||
// material-ui | |||
import { | |||
Box, | |||
Grid, Typography | |||
} from '@mui/material'; | |||
import ConsultantForm from "./ConsultantForm"; | |||
import {useContext, useEffect, useState} from "react"; | |||
import * as React from "react"; | |||
import axios from "axios"; | |||
import {apiPath} from "../../../auth/utils"; | |||
import { | |||
GET_CONSULTANT_PATH, | |||
} from "../../../utils/ApiPathConst"; | |||
import LoadingComponent from "../../extra-pages/LoadingComponent"; | |||
import {useLocation, useParams} from "react-router-dom"; | |||
import ApplicationTable from "./ApplicationTable"; | |||
import MainCard from "../../../components/MainCard"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
import {LIONER_FORM_THEME, CARD_MAX_WIDTH} from "../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {isObjEmpty} from "../../../utils/Utils"; | |||
import { el } from 'date-fns/locale'; | |||
const ConsultantPanel = () => { | |||
const { id } = useParams(); | |||
const location = useLocation(); | |||
const queryParams = new URLSearchParams(location.search); | |||
const [onReady, setOnReady] = useState(false); | |||
const [isNewRecord, setIsNewRecord] = useState(false); | |||
const [consultantDetail, setConsultantDetail] = useState({}); | |||
function getConsultantDetail(consultantId) { | |||
axios.get(`${apiPath}${GET_CONSULTANT_PATH}/${consultantId}`, | |||
) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
setConsultantDetail(response.data.data); | |||
console.log("response.data:" + response.data.name); | |||
// This is the correct place to set the ready state | |||
setOnReady(true); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
// Handle the error by still setting onReady to true, but maybe with a message | |||
setOnReady(true); | |||
return false; | |||
}); | |||
} | |||
useEffect(() => { | |||
// Check if id is defined and not null | |||
if(id){ | |||
// Check if it's a new record based on a negative ID | |||
if (Number(id) < 0) { | |||
setIsNewRecord(true); | |||
// Set ready state for new records immediately | |||
setOnReady(true); | |||
} else { | |||
setIsNewRecord(false); | |||
getConsultantDetail(id); | |||
} | |||
} else { | |||
// This block will handle cases where 'id' is undefined or null | |||
// If the route is /consultant without an ID, treat it as a new record. | |||
setIsNewRecord(true); | |||
setOnReady(true); | |||
} | |||
}, [id]); | |||
useEffect(() => { | |||
// This code will only run AFTER the 'onReady' state has been updated. | |||
if(consultantDetail) | |||
console.log("consultantDetail:" + consultantDetail.name); | |||
else | |||
console.log("consultantDetail null"); | |||
}, [onReady]); // The dependency array ensures this runs when onReady changes. | |||
useEffect(() => { | |||
// This code will only run AFTER the 'onReady' state has been updated. | |||
if (onReady === true) { | |||
console.log("The component is now ready!"); | |||
} | |||
}, [setConsultantDetail]); // The dependency array ensures this runs when onReady changes. | |||
return ( | |||
!onReady ? | |||
<LoadingComponent/> | |||
: | |||
<Grid container rowSpacing={3} columnSpacing={2.75} > | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<Grid item xs={12} s={12} md={12} lg={12}> | |||
<Grid container justifyContent="flex-start" alignItems="center"> | |||
<Grid item xs={3} sx={{mt:-1, mb: -2.25}} > | |||
<Box sx={{ display: 'flex', alignItems: 'center'}}> | |||
<Typography align="center" variant="h4" >{isNewRecord? "New Consultant" : "Maintain Consultant"}</Typography> | |||
</Box> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
{/*row 1*/} | |||
<Grid item xs={12} md={12} lg={12} > | |||
<ConsultantForm | |||
getConsultantDetail={getConsultantDetail} | |||
refConsultantDetail={consultantDetail} | |||
isNewRecord={isNewRecord} | |||
/> | |||
</Grid> | |||
</ThemeProvider> | |||
</Grid> | |||
); | |||
}; | |||
export default ConsultantPanel; |
@@ -0,0 +1,190 @@ | |||
// material-ui | |||
import { | |||
Button, | |||
Typography, | |||
Grid, TextField, InputLabel, CardActions | |||
} from '@mui/material'; | |||
import MainCard from "../../../components/MainCard"; | |||
import {useForm} from "react-hook-form"; | |||
import {useContext, useState} from "react"; | |||
import * as React from "react"; | |||
import {LIONER_BUTTON_THEME} from "../../../themes/colorConst"; | |||
import {useNavigate} from "react-router"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||
import dayjs from "dayjs"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
import {LIONER_FORM_THEME, CARD_MAX_WIDTH} from "../../../themes/themeConst"; | |||
import Collapse from '@mui/material/Collapse'; | |||
import {ExpandMore} from "@mui/icons-material"; | |||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |||
// ==============================|| Consultant - Search Form ||============================== // | |||
const ConsultantSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { | |||
const navigate = useNavigate() | |||
const ability = useContext(AbilityContext); | |||
const [isWindowOpen, setIsWindowOpen] = React.useState(false); | |||
const [createDateFrom, setCreateDateFrom] = useState(null); | |||
const [createDateTo, setCreateDateTo] = useState(null); | |||
const { setValue, reset, register, handleSubmit, getValues } = useForm() | |||
const handleExpandClick = () => { | |||
setExpanded(!expanded); | |||
}; | |||
const [createDateFromError, setCreateDateFromError] = React.useState(null); | |||
const [createDateToError, setCreateDateToError] = React.useState(null); | |||
const createDateFromErrorMessage = React.useMemo(() => { | |||
switch (createDateFromError) { | |||
case 'invalidDate': { | |||
return "Invalid date"; | |||
} | |||
} | |||
}, [createDateFromError]); | |||
const createDateToErrorMessage = React.useMemo(() => { | |||
switch (createDateToError) { | |||
case 'invalidDate': { | |||
return "Invalid date"; | |||
} | |||
} | |||
}, [createDateToError]); | |||
const createNewConsultant = () => { | |||
navigate(`/consultant/-1`); | |||
}; | |||
const onSubmit = (data) => { | |||
const criteria = { | |||
...data, | |||
createDateFrom: createDateFrom === null ? null : dayjs(createDateFrom).format('YYYY-MM-DD'), | |||
createDateTo: createDateTo === null ? null : dayjs(createDateTo).format('YYYY-MM-DD'), | |||
}; | |||
applySearch(criteria); | |||
}; | |||
function resetForm(){ | |||
setCreateDateFrom(null); | |||
setCreateDateTo(null); | |||
reset(); | |||
} | |||
const handleNewClick = () => { | |||
setIsWindowOpen(true); | |||
navigate(`/pdf/maintain/${id}`); | |||
}; | |||
const handleCloseClick = () => { | |||
setIsWindowOpen(false); | |||
} | |||
const handleCloseRefresh = () => { | |||
setIsWindowOpen(false); | |||
applySearch(); | |||
} | |||
return ( | |||
<MainCard xs={12} md={12} lg={12} | |||
content={false} | |||
sx={{width:CARD_MAX_WIDTH}} | |||
> | |||
<Grid item onClick={handleExpandClick}> | |||
<CardActions disableSpacing> | |||
{/*row 1*/} | |||
<Grid item justifyContent="space-between" alignItems="center" sx={{mt:1,ml:2,mb:1}}> | |||
<Typography variant="lionerSubLabel" > | |||
Search Criteria(s) | |||
</Typography> | |||
</Grid> | |||
<ExpandMore | |||
expand={expanded.toString()} | |||
onClick={handleExpandClick} | |||
aria-expanded={expanded} | |||
aria-label="show more" | |||
style={{ marginLeft: 'auto' }} // Align the button to the right | |||
> | |||
<ExpandMoreIcon /> | |||
</ExpandMore> | |||
</CardActions> | |||
</Grid> | |||
<Collapse in={expanded} timeout="auto" unmountOnExit> | |||
<form onSubmit={handleSubmit(onSubmit)}> | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
{/*row 2*/} | |||
<Grid container alignItems={"flex-start"} > | |||
<Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, mb:0.5}}> | |||
<InputLabel htmlFor="template">Name</InputLabel> | |||
<TextField | |||
fullWidth | |||
{...register("name")} | |||
id='name' | |||
inputProps={{maxLength: 255}} | |||
// label="Client Name" | |||
autoComplete="off" | |||
/> | |||
</Grid> | |||
</Grid> | |||
{/*last row*/} | |||
<Grid container maxWidth justifyContent="space-between" sx={{mt:1.5}}> | |||
<ThemeProvider theme={LIONER_BUTTON_THEME}> | |||
<Grid item> | |||
<Grid container> | |||
<Grid item sx={{ml:3, mr:1.5, mb:2}}> | |||
<Button | |||
variant="contained" | |||
type="submit" | |||
color="save" | |||
disabled={createDateFromError || createDateToError} | |||
onClick={applySearch} | |||
> | |||
Search | |||
</Button> | |||
</Grid> | |||
<Grid item sx={{ml:{xs:1.5, md:1.5, lg:1.5}, mr:1.5, mb:2}}> | |||
<Button | |||
variant="contained" | |||
color="cancel" | |||
onClick={resetForm} | |||
> | |||
Reset | |||
</Button> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
<Grid item> | |||
<Grid container> | |||
<Grid item sx={{ml:3, mr:3, mb:0.5}}> | |||
<Button | |||
variant="contained" | |||
color="create" | |||
onClick={createNewConsultant} | |||
> | |||
New Consultant | |||
</Button> | |||
</Grid> | |||
: | |||
<Grid/> | |||
</Grid> | |||
</Grid> | |||
</ThemeProvider> | |||
</Grid> | |||
</ThemeProvider> | |||
</form> | |||
</Collapse> | |||
</MainCard> | |||
); | |||
}; | |||
export default ConsultantSearchForm; |
@@ -0,0 +1,144 @@ | |||
// material-ui | |||
import * as React from 'react'; | |||
import { | |||
DataGrid, | |||
GridActionsCellItem, | |||
} from "@mui/x-data-grid"; | |||
import EditIcon from '@mui/icons-material/Edit'; | |||
import VisibilityIcon from '@mui/icons-material/Visibility'; | |||
import {useContext, useEffect} from "react"; | |||
import {useNavigate} from "react-router-dom"; | |||
import {CustomNoRowsOverlay, dateComparator, getDateString} from "../../../utils/CommonFunction"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
import {LIONER_BUTTON_THEME} from "../../../themes/colorConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
// ==============================|| Consultant TABLE ||============================== // | |||
export default function ConsultantTable({recordList, applySearch}) { | |||
const [rows, setRows] = React.useState(recordList); | |||
const [rowModesModel] = React.useState({}); | |||
const [isWindowOpen, setIsWindowOpen] = React.useState(false); | |||
const [recordId, setRecordId] = React.useState(null); | |||
const navigate = useNavigate() | |||
const ability = useContext(AbilityContext); | |||
const [paginationModel, setPaginationModel] = React.useState({ | |||
page: 0, | |||
pageSize:10 | |||
}); | |||
useEffect(() => { | |||
setPaginationModel({page:0,pageSize:10}); | |||
setRows(recordList); | |||
}, [recordList]); | |||
const handleEditClick = (id) => () => { | |||
navigate('/consultant/'+ id); | |||
}; | |||
const handleCloseClick = () => { | |||
setIsWindowOpen(false); | |||
setRecordId(null); | |||
}; | |||
const handleCloseRefresh = () => { | |||
setIsWindowOpen(false); | |||
setRecordId(null); | |||
applySearch(); | |||
}; | |||
const columns = [ | |||
/* | |||
{ | |||
field: 'actions', | |||
type: 'actions', | |||
headerName: 'Actions', | |||
// flex: 0.5, | |||
width: 100, | |||
cellClassName: 'actions', | |||
getActions: ({id}) => { | |||
return [ | |||
<ThemeProvider key="OutSave" theme={LIONER_BUTTON_THEME}> | |||
<GridActionsCellItem | |||
icon={<EditIcon sx={{fontSize: 25}}/>} | |||
label="Edit" | |||
className="textPrimary" | |||
onClick={handleEditClick(id)} | |||
color="edit" | |||
// disabled={'true'} | |||
// disabled={!ability.can('VIEW','DASHBOARD')} | |||
/> | |||
</ThemeProvider> | |||
] | |||
}, | |||
}, | |||
*/ | |||
// { | |||
// id: 'title', | |||
// field: 'title', | |||
// headerName: 'Title', | |||
// // sortComparator: dateComparator, | |||
// flex: 0.75, | |||
// renderCell: (params) => ( | |||
// <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
// {params.value} | |||
// </div> | |||
// // <div> | |||
// // {getDateString(params.row.pdfFrom,false)} | |||
// // </div> | |||
// ), | |||
// }, | |||
{ | |||
id: 'name', | |||
field: 'name', | |||
headerName: 'Name', | |||
flex: 2, | |||
renderCell: (params) => { | |||
return ( | |||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
{params.value} | |||
</div> | |||
); | |||
} | |||
}, | |||
{ | |||
id: 'createDate', | |||
field: 'createDate', | |||
headerName: 'Create Datetime', | |||
flex: 1, | |||
sortComparator: dateComparator, | |||
renderCell: (params) => ( | |||
<div> | |||
{getDateString(params.row.created, 'dd/MM/yyyy HH:mm:ss')} | |||
</div> | |||
), | |||
}, | |||
]; | |||
return ( | |||
<div> | |||
<DataGrid | |||
rows={rows} | |||
columns={columns} | |||
columnHeaderHeight={45} | |||
editMode="row" | |||
//autoPageSize | |||
rowModesModel={rowModesModel} | |||
getRowHeight={() => 'auto'} | |||
paginationModel={paginationModel} | |||
onPaginationModelChange={setPaginationModel} | |||
slots={{ | |||
noRowsOverlay: () => ( | |||
CustomNoRowsOverlay() | |||
) | |||
}} | |||
pageSizeOptions={[10]} | |||
autoHeight | |||
/> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,128 @@ | |||
// material-ui | |||
import { | |||
Grid, Typography | |||
} from '@mui/material'; | |||
import MainCard from "../../../components/MainCard"; | |||
import {useEffect, useState} from "react"; | |||
import axios from "axios"; | |||
import {apiPath} from "../../../auth/utils"; | |||
import { GET_CONSULTANT_PATH } from "../../../utils/ApiPathConst"; | |||
import * as React from "react"; | |||
import LoadingComponent from "../../extra-pages/LoadingComponent"; | |||
import ConsultantTable from "./ConsultantTable"; | |||
import ConsultantSearchForm from "./ConsultantSearchForm"; | |||
import Qs from "qs"; | |||
// import Autocomplete from "@mui/material/Autocomplete"; | |||
import {isObjEmpty} from "../../../utils/Utils"; | |||
import {isFormEmpty} from "../../../utils/CommonFunction"; | |||
// import UploadContext from "../../components/UploadProvider"; | |||
// import {useLocation} from "react-router-dom"; | |||
// import dayjs from "dayjs"; | |||
import {LIONER_FORM_THEME, CARD_MAX_WIDTH} from "../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {useParams} from "react-router-dom"; | |||
// ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
const ConsultantSearchPage = () => { | |||
const [onReady, setOnReady] = useState(false); | |||
const [expanded, setExpanded] = React.useState(true); | |||
const [record,setRecord] = useState([]); | |||
const [searchCriteria, setSearchCriteria] = useState({}); | |||
const params = useParams(); | |||
function getConsultantList() { | |||
axios.get(`${apiPath}${GET_CONSULTANT_PATH}`, { | |||
params: searchCriteria, | |||
// params: isInit? temp : searchCriteria, | |||
paramsSerializer: function (params) { | |||
return Qs.stringify(params, { arrayFormat: 'repeat' }); | |||
}, | |||
} | |||
) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
// if (!isFormEmpty(searchCriteria) && !isObjEmpty(response.data.records)) { | |||
// setExpanded(false); | |||
// } | |||
setRecord(response.data.records); | |||
setOnReady(true); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
} | |||
useEffect(() => { | |||
setSearchCriteria({clientId: params.id, | |||
...searchCriteria}); | |||
}, [params.id]); | |||
useEffect(() => { | |||
if (!isObjEmpty(searchCriteria)) { | |||
getConsultantList(); | |||
} | |||
}, [searchCriteria]); | |||
function applySearch(input) { | |||
setSearchCriteria({clientId: params.id, | |||
...input}); | |||
} | |||
return ( | |||
<Grid container rowSpacing={3} columnSpacing={2.75} > | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<Grid item xs={12} md={12} lg={12} > | |||
<Grid container maxWidth justifyContent="space-between" sx={{mt:-2, width:CARD_MAX_WIDTH}} > | |||
<Grid item xs={4} s={4} md={4} lg={4} | |||
sx={{ mb: -2.25, display: 'flex', alignItems: 'center'}}> | |||
<Typography variant="h4">Manage Consultant</Typography> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
{/* Search Form */} | |||
<Grid item xs={12} md={12} lg={12}> | |||
<ConsultantSearchForm | |||
// isUpdating={isUpdating} | |||
// setIsUpdating={setIsUpdating} | |||
applySearch={applySearch} | |||
// refConsultantData={refConsultantData} | |||
// getConsultantList={getConsultantList} | |||
setExpanded={setExpanded} | |||
expanded={expanded} | |||
clientId={params.id} | |||
// userDivision={userDivision} | |||
/> | |||
</Grid> | |||
{!onReady? <LoadingComponent/> : | |||
// Consultant Table | |||
<Grid item xs={12} md={12} lg={12}> | |||
<MainCard elevation={0} | |||
content={false} | |||
sx={{mt:{lg:-1.5}, width: CARD_MAX_WIDTH}} | |||
> | |||
<div style={{/*height: expanded? '46vh' : '75vh',*/ width: '100%'}}> | |||
<ConsultantTable | |||
recordList={record} | |||
pageSize={10} | |||
expanded={expanded} | |||
clientId={params.id} | |||
applySearch={applySearch} | |||
/> | |||
</div> | |||
</MainCard> | |||
</Grid> | |||
} | |||
</ThemeProvider> | |||
</Grid> | |||
); | |||
}; | |||
export default ConsultantSearchPage; |
@@ -233,19 +233,17 @@ const TemplateSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { | |||
<Grid item> | |||
<Grid container> | |||
{ability.can('EDIT','EVENT') ? | |||
<Grid item sx={{ml:3, mr:3, mb:0.5}}> | |||
<Button | |||
variant="contained" | |||
color="create" | |||
onClick={handleNewClick} | |||
> | |||
New Template | |||
</Button> | |||
</Grid> | |||
: | |||
<Grid/> | |||
} | |||
<Grid item sx={{ml:3, mr:3, mb:0.5}}> | |||
<Button | |||
variant="contained" | |||
color="create" | |||
onClick={handleNewClick} | |||
> | |||
New Template | |||
</Button> | |||
</Grid> | |||
: | |||
<Grid/> | |||
</Grid> | |||
</Grid> | |||
</ThemeProvider> | |||
@@ -12,6 +12,8 @@ const SettingPage = Loadable(lazy(() => import('pages/lionerSettingPage'))); | |||
const LogoutPage = Loadable(lazy(() => import('pages/extra-pages/LogoutPage'))); | |||
const PasswordPolicyPage = Loadable(lazy(()=> import('pages/lionerPasswordPolicyPage'))) | |||
const UserSearchPage = Loadable(lazy(()=>import ('pages/lionerUserSearchPage'))); | |||
const ConsultantSearchPage = Loadable(lazy(()=>import ('pages/pdf/ConsultantSearchPage'))); | |||
const ConsultantMaintainPage = Loadable(lazy(() => import('pages/pdf/ConsultantMaintainPage'))); | |||
const CategoryPage = Loadable(lazy(()=>import ('pages/lionerCategoryPage'))); | |||
const UserMaintainPage = Loadable(lazy(() => import('pages/lionerUserDetailPage'))); | |||
const UserGroupSearchPage = Loadable(lazy(() => import('pages/lionerUserGroupSearchPage'))); | |||
@@ -134,6 +136,27 @@ const SettingRoutes = () => { | |||
) | |||
), | |||
}, | |||
{ | |||
path: 'consultant', | |||
element: ( | |||
handleRouteAbility( | |||
ability.can('VIEW', 'USER'), | |||
<ConsultantSearchPage />, | |||
<Navigate to="/" /> | |||
) | |||
), | |||
}, | |||
{ | |||
path: 'consultant/:id', | |||
element: ( | |||
handleRouteAbility( | |||
ability.can('VIEW', 'USER'), | |||
<ConsultantMaintainPage />, | |||
<Navigate to="/" /> | |||
) | |||
), | |||
}, | |||
{ | |||
path: 'setting', | |||
element: ( | |||
@@ -61,6 +61,9 @@ export const GET_THUMBNAIL_PATH = "/file/thumbnail" | |||
export const POST_THUMBNAIL_PATH = "/file/thumbnail/ul" | |||
export const GET_TEMPLATE_PATH = "/template" | |||
export const POST_TEMPLATE_PATH = "/template/save" | |||
export const GET_CONSULTANT_PATH = "/consultant" | |||
export const POST_CONSULTANT_PATH = "/consultant/save" | |||
export const GET_CONSULTANT_COMBO_LIST = "/consultant/combo" | |||
export const GET_PDF_PATH = "/pdf" | |||
export const POST_PDF_PATH = "/pdf/save" | |||
export const POST_UPLOAD_PDF_PATH = "/pdf2/upload" | |||