@@ -105,6 +105,15 @@ const setting = { | |||||
breadcrumbs: false, | breadcrumbs: false, | ||||
ability:['VIEW','USER'] | ability:['VIEW','USER'] | ||||
}, | }, | ||||
{ | |||||
id: 'consultant', | |||||
title: 'Consultant', | |||||
type: 'item', | |||||
url: '/consultant', | |||||
icon: icons.UserOutlined, | |||||
breadcrumbs: false, | |||||
ability:['VIEW','USER'] | |||||
}, | |||||
// { | // { | ||||
// id: 'auditLog', | // id: 'auditLog', | ||||
// title: 'Audit Log', | // 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 item> | ||||
<Grid container> | <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> | ||||
</Grid> | </Grid> | ||||
</ThemeProvider> | </ThemeProvider> | ||||
@@ -12,6 +12,8 @@ const SettingPage = Loadable(lazy(() => import('pages/lionerSettingPage'))); | |||||
const LogoutPage = Loadable(lazy(() => import('pages/extra-pages/LogoutPage'))); | const LogoutPage = Loadable(lazy(() => import('pages/extra-pages/LogoutPage'))); | ||||
const PasswordPolicyPage = Loadable(lazy(()=> import('pages/lionerPasswordPolicyPage'))) | const PasswordPolicyPage = Loadable(lazy(()=> import('pages/lionerPasswordPolicyPage'))) | ||||
const UserSearchPage = Loadable(lazy(()=>import ('pages/lionerUserSearchPage'))); | 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 CategoryPage = Loadable(lazy(()=>import ('pages/lionerCategoryPage'))); | ||||
const UserMaintainPage = Loadable(lazy(() => import('pages/lionerUserDetailPage'))); | const UserMaintainPage = Loadable(lazy(() => import('pages/lionerUserDetailPage'))); | ||||
const UserGroupSearchPage = Loadable(lazy(() => import('pages/lionerUserGroupSearchPage'))); | 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', | path: 'setting', | ||||
element: ( | element: ( | ||||
@@ -61,6 +61,9 @@ export const GET_THUMBNAIL_PATH = "/file/thumbnail" | |||||
export const POST_THUMBNAIL_PATH = "/file/thumbnail/ul" | export const POST_THUMBNAIL_PATH = "/file/thumbnail/ul" | ||||
export const GET_TEMPLATE_PATH = "/template" | export const GET_TEMPLATE_PATH = "/template" | ||||
export const POST_TEMPLATE_PATH = "/template/save" | 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 GET_PDF_PATH = "/pdf" | ||||
export const POST_PDF_PATH = "/pdf/save" | export const POST_PDF_PATH = "/pdf/save" | ||||
export const POST_UPLOAD_PDF_PATH = "/pdf2/upload" | export const POST_UPLOAD_PDF_PATH = "/pdf2/upload" | ||||