From 03e2632cfde9fcbb2f82944a8c38cb21a65884b1 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Mon, 18 Aug 2025 21:11:07 +0800 Subject: [PATCH] no message --- src/menu-items/setting.js | 9 + .../client/ClientMaintainPage/ClientForm.js | 880 ++++-------------- .../ApplicationTable.js | 122 +++ .../ConsultantMaintainPage/ConsultantForm.js | 363 ++++++++ src/pages/pdf/ConsultantMaintainPage/index.js | 119 +++ .../ConsultantSearchForm.js | 190 ++++ .../ConsultantSearchPage/ConsultantTable.js | 144 +++ src/pages/pdf/ConsultantSearchPage/index.js | 128 +++ .../TemplateSearchPage/TemplateSearchForm.js | 24 +- src/routes/SettingRoutes.js | 23 + src/utils/ApiPathConst.js | 3 + 11 files changed, 1312 insertions(+), 693 deletions(-) create mode 100644 src/pages/pdf/ConsultantMaintainPage/ApplicationTable.js create mode 100644 src/pages/pdf/ConsultantMaintainPage/ConsultantForm.js create mode 100644 src/pages/pdf/ConsultantMaintainPage/index.js create mode 100644 src/pages/pdf/ConsultantSearchPage/ConsultantSearchForm.js create mode 100644 src/pages/pdf/ConsultantSearchPage/ConsultantTable.js create mode 100644 src/pages/pdf/ConsultantSearchPage/index.js diff --git a/src/menu-items/setting.js b/src/menu-items/setting.js index 679100f..8432005 100644 --- a/src/menu-items/setting.js +++ b/src/menu-items/setting.js @@ -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', diff --git a/src/pages/client/ClientMaintainPage/ClientForm.js b/src/pages/client/ClientMaintainPage/ClientForm.js index cdfc6cf..bf7a5ba 100644 --- a/src/pages/client/ClientMaintainPage/ClientForm.js +++ b/src/pages/client/ClientMaintainPage/ClientForm.js @@ -1,4 +1,3 @@ -// material-ui import { Button, FormControlLabel, @@ -24,8 +23,9 @@ import Autocomplete from "@mui/material/Autocomplete"; import axios from "axios"; import {apiPath} from "../../../auth/utils"; import { - CHECK_EVENT_DUPLICATE, //GET_EVENT_NOTIFICATION_OVERTIME, + CHECK_EVENT_DUPLICATE, GET_CLIENT_PATH, + GET_CONSULTANT_COMBO_LIST, GET_SUB_DIVISION_COMBO_LIST, POST_CLIENT_PATH } from "../../../utils/ApiPathConst"; @@ -42,12 +42,7 @@ import {isObjEmpty} from "../../../utils/Utils"; import AbilityContext from "../../../components/AbilityProvider"; import {CARD_MAX_WIDTH} from "../../../themes/themeConst"; -// ==============================|| DASHBOARD - DEFAULT ||============================== // - -const ClientForm = ({ refClientDetail, - isNewRecord, - getClientDetail, -}) => { +const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { const location = useLocation(); const queryParams = new URLSearchParams(location.search); const refId = queryParams.get("refId"); @@ -64,9 +59,10 @@ const ClientForm = ({ refClientDetail, const [isEditing, setIsEditing] = useState(false); const { setIsUploading } = useContext(UploadContext); const [refClient, setRefClient] = useState({}); + const [consultantComboList, setConsultantComboList] = useState([]); - //form data - const {register, getValues, setValue} = useForm(); + // Form data + const { register, getValues, setValue } = useForm(); const [dob, setDob] = useState(null); const [dobError, setDobError] = React.useState(null); @@ -75,7 +71,6 @@ const ClientForm = ({ refClientDetail, const [announceDateError, setAnnounceDateError] = React.useState(null); const [fromError, setFromError] = React.useState(null); const [toError, setToError] = React.useState(null); - // const [nextApplicationDateError, setNextApplicationDateError] = React.useState(null); const dobErrorMessage = React.useMemo(() => { switch (dobError) { @@ -125,14 +120,7 @@ const ClientForm = ({ refClientDetail, } }, [toError]); - // const nextApplicationDateErrorMessage = React.useMemo(() => { - // switch (nextApplicationDateError) { - // case 'invalidDate': { - // return "Invalid date"; - // } - // } - // }, [nextApplicationDateError]); -// ==============================|| DELETE WINDOW RELATED ||============================== // + // DELETE WINDOW RELATED const [isWindowOpen, setIsWindowOpen] = React.useState(false); const handleClose = () => { @@ -145,58 +133,10 @@ const ClientForm = ({ refClientDetail, const copyClientAsNew = () => { navigate(`/event/maintain/-1?refId=${params.id}`); - } - - // useEffect(()=>{ - // if(applicationList.length===0){ - // validateReminder(); - // } - // },[dob, nextApplicationDate, deadlineDate]); - - // const validateReminder = () =>{ - // const values = getValues(); - // let firstIssueDate = null; - // let reminderDates = []; - // if (dob !== null && - // nextApplicationDate !== null && - // values.reminderThreshold !== null && - // values.reminderInterval !== null && - // values.reminderLimit !== null - // ) { - // //have value - // if(applicationList.length <=0){ - // firstIssueDate = dob.add(-values.reminderThreshold,'days'); - // } - // else{ - // firstIssueDate = nextApplicationDate.add(-values.reminderThreshold, 'days'); - // } - - // reminderDates.push(firstIssueDate); - // for(let i=1; i < parseInt(values.reminderLimit)+1 ; i++){ - // reminderDates.push(firstIssueDate.add(i*values.reminderInterval,'days')) - // } - - // for(let j=0; j < reminderDates.length ; j++){ - // if(reminderDates[j]-deadlineDate > 0){ - // setOvertimeWarning(`Estimated reminder exceeds the application deadline, the last reminder will be generated on ${reminderDates[j>1?j-1:0].format('DD/MM/YYYY')}.`); - // return false; - // } - // } - - // // setOvertimeWarning(""); - // return true; - // } - // return true; - // } + }; - function updateData(){ - // if(applicationList.length >0){ - // notifyDeleteError("Delete restricted. Application exist in this event"); - // setIsWindowOpen(false); - // } - // else{ - axios.delete(`${apiPath}${GET_CLIENT_PATH}/${params.id}`, - ) + function updateData() { + axios.delete(`${apiPath}${GET_CLIENT_PATH}/${params.id}`) .then((response) => { if (response.status === 204) { notifyDeleteSuccess(); @@ -208,163 +148,61 @@ const ClientForm = ({ refClientDetail, console.log(error); return false; }); - // } - } -// ==============================|| DELETE WINDOW RELATED ||============================== // - - // const validateReminderLimit = (value) => { - // const maxValue = reminderLimitMax; // Set the maximum value here - // if (/^[0-9]\d*$/.test(value) || value === '') { - // if (parseInt(value) > maxValue) { - // //console.log(value); - // setValue("reminderLimit", maxValue) - // } - // } - // else{ - // setValue("reminderLimit", ''); - // } - // }; - - // const handleReminderIntervalChange = (event) => { - // const inputValue = event.target.value; - // if (/^[0-9]\d*$/.test(inputValue) || inputValue === '') { - // setValue("reminderInterval", inputValue); - // } - // else{ - // setValue("reminderInterval", ''); - // } - // }; - // const handleReminderThresholdChange = (event) => { - // const inputValue = event.target.value; - // if (/^[0-9]\d*$/.test(inputValue) || inputValue === '') { - // setValue("reminderThreshold", inputValue); - // } - // else{ - // setValue("reminderThreshold", ''); - // } - // }; + function getConsultantCombo() { + axios.get(`${apiPath}${GET_CONSULTANT_COMBO_LIST}`, { + params: {} + }) + .then((response) => { + if (response.status === 200) { + setConsultantComboList(response.data.records); + } + }) + .catch(error => { + console.log(error); + return false; + }); + } - useEffect(()=>{ + useEffect(() => { if (!isNewRecord) { setRefClient(refClientDetail); } - // //if combo list ready - // if (subDivisionList.length >0) { - // } - },[]); + getConsultantCombo(); + }, []); - // useEffect(()=>{ - // if(!isObjEmpty(refClient)){ - // setIsFlagFetched(true); - // } - // },[reminderFlag,refClient]); - - // useEffect(()=>{ - // if(isFlagFetched){ - // if(!reminderFlag){ - // setReminderInterval(null); - // setReminderBefore(null); - // setReminderLimit(null); - // setValue("reminderThreshold",null); - // setValue("reminderLimit",null); - // setValue("reminderInterval",null); - // } - // } - // },[isFlagFetched, refClient, reminderFlag]); - - // useEffect(()=>{ - // setReminderLimitMax(refReminderLimitMax); - // if(!isObjEmpty(refClient) ) { - // if(reminderFlag){ - // setReminderInterval(refReminderInterval); - // setReminderBefore(refReminderBefore); - // setReminderLimit(refReminderLimit); - // } - // } - // },[reminderFlag, refReminderLimit,refReminderInterval,refReminderLimitMax,refReminderBefore,refClient]); - - useEffect(()=>{ - //if ref data ready + useEffect(() => { if (!isObjEmpty(refClient)) { - //checkOvertime(); - if(refId !== null){ - setValue("lastname", refClient.lastname); - setValue("firstname", refClient.firstname); - setValue("email", refClient.email); - setValue("phone1", refClient.phone1); - setValue("phone2", refClient.phone2); - setValue("remarks", refClient.remarks); - setValue("caseManagerId", 1); - setValue("consultantId", 1); - } + setValue("lastname", refClient.lastname); + setValue("firstname", refClient.firstname); + setValue("email", refClient.email); + setValue("phone1", refClient.phone1); + setValue("phone2", refClient.phone2); + setValue("remarks", refClient.remarks); + setValue("caseManagerId", 1); + + // Set consultantId for Autocomplete + const selectedConsultant = consultantComboList.find( + (option) => option.id === refClient.consultantId + ); + setValue("consultantId", selectedConsultant || null); + setDob(dayjs(getDateString(refClient.dob))); } - },[refClient]); + }, [refClient, consultantComboList]); - useEffect(()=>{ - if(!isObjEmpty(refClient)) { + useEffect(() => { + if (!isObjEmpty(refClient)) { setOnReady(true); - } - else if(isNewRecord){ + } else if (isNewRecord) { setOnReady(true); setIsEditing(true); } - },[refClient]); - // },[refClient,selectedSubDivision,selectedRegion,selectedType, dob, deadlineDate]); - - // useEffect(()=>{ - // if(selectedFrequency !== null){ - // if(selectedFrequency.key === 5 && isNewRecord){ - // } - // else if (isFirstInit && refId !== null){ - // setIsFirstInit(false); - // } - // else if(isNewRecord){ - // } - - // if (isEditing){ - // if (dob !== null){ - // switch(selectedFrequency.key){ - // case 1: - // setNextApplicationDate(dob.add(1,'month')); - // break; - // case 2: - // setNextApplicationDate(dob.add(3,'month')); - // break; - // case 3: - // setNextApplicationDate(dob.add(1,'year')); - // break; - // case 4: - // setNextApplicationDate(dob.add(6,'month')); - // break; - // default: - // setNextApplicationDate(nextApplicationDate); - // break; - // } - // } - // } - // } - // },[selectedFrequency,dob]); - - // useEffect(() => { - // //if state data are ready and assign to different field - // axios.get(`${apiPath}${GET_SUB_DIVISION_COMBO_LIST}`) - // .then((response) => { - // if (response.status === 200) { - // setSubDivisionList(response.data.records); - // } - // }) - // .catch(error => { - // console.log(error); - // return false; - // }); - // }, []); + }, [refClient]); useEffect(() => { - //upload latest data to parent - if(isCollectData){ + if (isCollectData) { const values = getValues(); const formErrors = {}; @@ -372,127 +210,31 @@ const ClientForm = ({ refClientDetail, formErrors.lastname = 'Last Name is required'; } - // if (selectedRegion === null ) { - // formErrors.region = 'Client Region is required'; - // } - - // if (dob === null) { - // formErrors.dob = 'DoB is required'; - // } - - // if (deadlineDate === null) { - // formErrors.deadlineDate = 'Deadline Date is required'; - // } - - // if (awardDate === null) { - // formErrors.awardDate = 'Award Date is required'; - // } - - // if (selectedSubDivision.length <= 0){ - // formErrors.subDivision = 'Sub-Division is required'; - // } - - // if(reminderFlag){ - // if (selectedFrequency === null){ - // formErrors.frequency = 'Frequency is required'; - // } - - // if (!values.reminderThreshold){ - // formErrors.threshold = 'Threshold is required'; - // } - - // if (!values.reminderInterval){ - // formErrors.interval = 'Interval is required'; - // } - - // if (!values.reminderLimit){ - // formErrors.limit = 'Limit is required'; - // } - - - // if(applicationList.length===0){ - // if(!validateReminder()){ - // formErrors.calError = "calError"; - // } - // } - - // } - setErrors(formErrors); - // if (Object.keys(formErrors).length === 0 //&& - // !dobError && !awardDateError && - // !deadlineDateError && !announceDateError && - // !fromError && !toError && !nextApplicationDateError - // ) { - // axios.get(`${apiPath}${CHECK_EVENT_DUPLICATE}`, - // { - // params: { - // "fullname": values.fullname.trim(), - // "id": params.id, - // }, - // }) - // .then((response) => { - // if (response.status === 200) { - // const formErrors = {}; - // if(response.data.isTaken){ - // formErrors.fullname = 'Client name must be unique.'; - // setErrors(formErrors); - // setIsCollectData(false); - // setUserConfirm(false); - // } - // else if(!response.data.isTaken){ - // if (Object.keys(formErrors).length === 0) { - // let data = {}; - // data["id"] = isNewRecord ? params.id : refClientDetail.id; - // data["fullname"] = values.fullname; - // data["lastname"] = values.lastname; - // data["firstname"] = values.firstname; - // data["title"] = values.title; - // data["email"] = values.email; - // data["phone1"] = values.phone1; - // data["phone2"] = values.phone2; - // data["remarks"] = values.remarks; - // data["dob"] = dob === null ? null : dayjs(dob).format('YYYY-MM-DD'); - // setClientDetail(data); - // } - // } - // } - // }) - // .catch(error => { - // console.log(error); - // return true; - // }); if (Object.keys(formErrors).length === 0) { let data = {}; - data["id"] = isNewRecord ? params.id : refClientDetail.id; - data["lastname"] = values.lastname; + data["id"] = isNewRecord ? params.id : refClientDetail.id; + data["lastname"] = values.lastname; data["firstname"] = values.firstname; - data["email"] = values.email; - data["phone1"] = values.phone1; - data["phone2"] = values.phone2; - data["remarks"] = values.remarks; - data["caseManagerId"] = 1; - data["consultantId"] = 1; - data["dob"] = dob === null ? null : dayjs(dob).format('YYYY-MM-DD'); + data["email"] = values.email; + data["phone1"] = values.phone1; + data["phone2"] = values.phone2; + data["remarks"] = values.remarks; + data["caseManagerId"] = 1; + data["consultantId"] = values.consultantId ? values.consultantId.id : null; + data["dob"] = dob === null ? null : dayjs(dob).format('YYYY-MM-DD'); setClientDetail(data); - } - else if(isCollectData){ + } else if (isCollectData) { setUserConfirm(false); setIsCollectData(false); } - // setUserConfirm(false); - // setIsCollectData(false); } }, [isCollectData]); - function returnSearchPage(){ + function returnSearchPage() { navigate('/client'); } - // function createNewApplication(){ - // navigate('/application/maintain/-1/'+ params.id); - // } - useEffect(() => { if (userConfirm) { postClient(); @@ -500,43 +242,35 @@ const ClientForm = ({ refClientDetail, setUserConfirm(false); }, [clientDetail]); - const submitData = () => { setIsCollectData(!isCollectData); setUserConfirm(true); - } + }; const updateIsEdit = () => { setIsEditing(!isEditing); - } + }; - function postClient(){ + function postClient() { setIsUploading(true); const temp = trimDataBeforePost(clientDetail); - // console.log("Posting:"); - // console.log(clientDetail); - axios.post(`${apiPath}${POST_CLIENT_PATH}`, - temp, - ) + axios.post(`${apiPath}${POST_CLIENT_PATH}`, temp) .then((response) => { if (response.status === 200) { notifySaveSuccess(); - if(isNewRecord){ + if (isNewRecord) { setIsUploading(false); - navigate('/client') - } - else{ + navigate('/client'); + } else { setIsUploading(false); getClientDetail(params.id); - //checkOvertime(); setIsEditing(!isEditing); } setIsCollectData(!isCollectData); - } }) .catch(error => { - if (error.response.status === 422){ + if (error.response.status === 422) { const formErrors = {}; formErrors.subDivision = error.response.data.error; setErrors(formErrors); @@ -547,43 +281,31 @@ const ClientForm = ({ refClientDetail, }); } - return ( !onReady ? - + : - - + + Information -
- + - + Client Code: * - - + - - + - + Last Name: * - - + - - + - + First Name: - - + - - {/* - - - - First Name: * - - - - - { - setSelectedRegion(newValue); - }} - renderInput={(params) => } - disabled={!isEditing} - isOptionEqualToValue={isOptionEqualToValue} - /> - - - */} - - + - + Date of Birth: - - + - + - + - - + - + - Email Address: + Email Address: - - + - - + - + - Phone Number: + Phone Number: - - + - - {/* - - - - Phone Number (Secondary): - - - - - - - - */} - - + - + Case Manager: - - + - - {/* - option.label} - onChange={(event, newValue) => { - setSelectedSubDivision(newValue); - }} - renderInput={(params) => ( - - {errors.subDivision.split("\n").map((line, index) => { - return ( - - {line} - - ) - })} - - ) : null - } - {...params} - placeholder="" - /> - )} - disabled={!isEditing} - isOptionEqualToValue={isOptionEqualToValue} - /> - */} - - + - + Consultant: - - - - - - {/* + option.label} + id="consultant-combo-box" + options={consultantComboList} + getOptionLabel={(option) => option.name || ""} + isOptionEqualToValue={(option, value) => option.id === value.id} + value={getValues("consultantId") || null} onChange={(event, newValue) => { - setSelectedSubDivision(newValue); + setValue('consultantId', newValue || null); }} renderInput={(params) => ( - {errors.subDivision.split("\n").map((line, index) => { - return ( - - {line} - - ) - })} - - ) : null - } {...params} - placeholder="" + label="Consultant" + name="consultant" /> )} disabled={!isEditing} - isOptionEqualToValue={isOptionEqualToValue} /> - */} + - - + - + Remarks: - - + - - {/*row 2*/} - { - (isEditing ) ? - - - - - - - - + {(isEditing) ? + + + + + + + + + + + + {!isNewRecord && ( - - - - {!isNewRecord && ( - )} - - + )} + - - - - - {/* - - */} - - {/* - - */} - - + + + + + - - - : - - - - - - - - - - + + + + : + + + + + + + + + - - - - + + + + + } -
- - ); }; -export default ClientForm; +export default ClientForm; \ No newline at end of file diff --git a/src/pages/pdf/ConsultantMaintainPage/ApplicationTable.js b/src/pages/pdf/ConsultantMaintainPage/ApplicationTable.js new file mode 100644 index 0000000..8b73ed4 --- /dev/null +++ b/src/pages/pdf/ConsultantMaintainPage/ApplicationTable.js @@ -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 [ + } + 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 ( +
+ {params.value} +
+ ); + } + }, + { + id: 'subDivisions', + field: 'subDivisions', + //type: 'date', + //sortable: false, + headerName: 'Sub-Divisions', + flex: 1, + renderCell: (params) => { + return ( +
+ {params.value} +
+ ); + } + }, + { + id: 'tags', + field: 'tags', + //type: 'date', + //sortable: false, + headerName: 'Tags', + flex: 0.8, + }, + { + id: 'responsibleOfficer', + field: 'responsibleOfficer', + headerName: 'Responsible Officer', + flex: 0.7, + }, + ]; + + return ( +
+ 'auto'} + initialState={{ + pagination: { + paginationModel: {page: 0, pageSize: 5}, + }, + }} + slots={{ + noRowsOverlay: () => ( + CustomNoRowsOverlay() + ) + }} + pageSizeOptions={[5]} + autoHeight + /> +
+ ); +} diff --git a/src/pages/pdf/ConsultantMaintainPage/ConsultantForm.js b/src/pages/pdf/ConsultantMaintainPage/ConsultantForm.js new file mode 100644 index 0000000..e84b63a --- /dev/null +++ b/src/pages/pdf/ConsultantMaintainPage/ConsultantForm.js @@ -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 ? + + : + + +
+ + + + + + + Name: + + + + + + + + + {/*row 2*/} + { + (isEditing ) ? + + + + + + + + + + + + + {!isNewRecord && ( + )} + + + + + + + + + {/* + + */} + + {/* + + */} + + + + + + : + + + + + + + + + + + + + + + + + } + + + +
+
+ + + ); +}; + +export default ConsultantForm; diff --git a/src/pages/pdf/ConsultantMaintainPage/index.js b/src/pages/pdf/ConsultantMaintainPage/index.js new file mode 100644 index 0000000..0f9617e --- /dev/null +++ b/src/pages/pdf/ConsultantMaintainPage/index.js @@ -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 ? + + : + + + + + + + {isNewRecord? "New Consultant" : "Maintain Consultant"} + + + + + + {/*row 1*/} + + + + + + ); +}; + +export default ConsultantPanel; \ No newline at end of file diff --git a/src/pages/pdf/ConsultantSearchPage/ConsultantSearchForm.js b/src/pages/pdf/ConsultantSearchPage/ConsultantSearchForm.js new file mode 100644 index 0000000..a84619f --- /dev/null +++ b/src/pages/pdf/ConsultantSearchPage/ConsultantSearchForm.js @@ -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 ( + + + + {/*row 1*/} + + + Search Criteria(s) + + + + + + + + + + +
+ + {/*row 2*/} + + + Name + + + + + + + {/*last row*/} + + + + + + + + + + + + + + + + + + + + : + + + + + + +
+
+
+ ); +}; + +export default ConsultantSearchForm; diff --git a/src/pages/pdf/ConsultantSearchPage/ConsultantTable.js b/src/pages/pdf/ConsultantSearchPage/ConsultantTable.js new file mode 100644 index 0000000..eef8a0c --- /dev/null +++ b/src/pages/pdf/ConsultantSearchPage/ConsultantTable.js @@ -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 [ + + } + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="edit" + // disabled={'true'} + // disabled={!ability.can('VIEW','DASHBOARD')} + /> + + ] + }, + }, + */ + // { + // id: 'title', + // field: 'title', + // headerName: 'Title', + // // sortComparator: dateComparator, + // flex: 0.75, + // renderCell: (params) => ( + //
+ // {params.value} + //
+ // //
+ // // {getDateString(params.row.pdfFrom,false)} + // //
+ // ), + // }, + { + id: 'name', + field: 'name', + headerName: 'Name', + flex: 2, + renderCell: (params) => { + return ( +
+ {params.value} +
+ ); + } + }, + { + id: 'createDate', + field: 'createDate', + headerName: 'Create Datetime', + flex: 1, + sortComparator: dateComparator, + renderCell: (params) => ( +
+ {getDateString(params.row.created, 'dd/MM/yyyy HH:mm:ss')} +
+ ), + }, + + ]; + + return ( +
+ 'auto'} + paginationModel={paginationModel} + onPaginationModelChange={setPaginationModel} + slots={{ + noRowsOverlay: () => ( + CustomNoRowsOverlay() + ) + }} + pageSizeOptions={[10]} + autoHeight + /> +
+ ); +} diff --git a/src/pages/pdf/ConsultantSearchPage/index.js b/src/pages/pdf/ConsultantSearchPage/index.js new file mode 100644 index 0000000..906a3ff --- /dev/null +++ b/src/pages/pdf/ConsultantSearchPage/index.js @@ -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 ( + + + + + + Manage Consultant + + + + + {/* Search Form */} + + + + + {!onReady? : + // Consultant Table + + +
+ +
+
+
+ } +
+
+ + ); +}; + +export default ConsultantSearchPage; \ No newline at end of file diff --git a/src/pages/pdf/TemplateSearchPage/TemplateSearchForm.js b/src/pages/pdf/TemplateSearchPage/TemplateSearchForm.js index 0c9f9e6..bed1f4e 100644 --- a/src/pages/pdf/TemplateSearchPage/TemplateSearchForm.js +++ b/src/pages/pdf/TemplateSearchPage/TemplateSearchForm.js @@ -233,19 +233,17 @@ const TemplateSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { - {ability.can('EDIT','EVENT') ? - - - - : - - } + + + + : + diff --git a/src/routes/SettingRoutes.js b/src/routes/SettingRoutes.js index b677467..9193c80 100644 --- a/src/routes/SettingRoutes.js +++ b/src/routes/SettingRoutes.js @@ -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'), + , + + ) + ), + }, + { + path: 'consultant/:id', + element: ( + handleRouteAbility( + ability.can('VIEW', 'USER'), + , + + ) + ), + }, + { path: 'setting', element: ( diff --git a/src/utils/ApiPathConst.js b/src/utils/ApiPathConst.js index a28ee38..0b5841f 100644 --- a/src/utils/ApiPathConst.js +++ b/src/utils/ApiPathConst.js @@ -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"