diff --git a/src/menu-items/client.js b/src/menu-items/client.js index be3c2f1..f0fff82 100644 --- a/src/menu-items/client.js +++ b/src/menu-items/client.js @@ -1,6 +1,7 @@ // assets import BoyIcon from '@mui/icons-material/Boy'; import AssignmentIcon from '@mui/icons-material/Assignment'; +import PictureAsPdfOutlinedIcon from '@mui/icons-material/PictureAsPdfOutlined'; // icons @@ -12,6 +13,7 @@ const icons = { ) }, AssignmentIcon, + FormSigPageIcon: PictureAsPdfOutlinedIcon, }; // ==============================|| MENU ITEMS - DASHBOARD ||============================== // @@ -40,6 +42,15 @@ const client = { breadcrumbs: false, ability:['VIEW','TEMPLATE'] }, + { + id: 'formSigPage', + title: 'Form Sig Page', + type: 'item', + url: '/formSigPage', + icon: icons.FormSigPageIcon, + breadcrumbs: false, + ability:['VIEW','TEMPLATE'] + }, ] }; diff --git a/src/menu-items/setting.js b/src/menu-items/setting.js index ab652ce..9d6bbff 100644 --- a/src/menu-items/setting.js +++ b/src/menu-items/setting.js @@ -13,8 +13,7 @@ import { MenuUnfoldOutlined, FileSearchOutlined, MailOutlined, - ApartmentOutlined, - FilePdfOutlined + ApartmentOutlined } from '@ant-design/icons'; // icons @@ -32,8 +31,7 @@ const icons = { MenuUnfoldOutlined, FileSearchOutlined, MailOutlined, - ApartmentOutlined, - FilePdfOutlined + ApartmentOutlined }; // ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // @@ -143,15 +141,6 @@ const setting = { breadcrumbs: false, ability:['VIEW','USER'] }, - { - id: 'formSigPage', - title: 'Form Sig Page', - type: 'item', - url: '/formSigPage', - icon: icons.FilePdfOutlined, - breadcrumbs: false, - ability:['MANAGE','SYSTEM_CONFIGURATION'] - }, // { // id: 'passwordPolicy', // title: 'Password Policy', diff --git a/src/pages/formSigPage/FormSigPageSearchForm.js b/src/pages/formSigPage/FormSigPageSearchForm.js index 39a3f70..acf41c7 100644 --- a/src/pages/formSigPage/FormSigPageSearchForm.js +++ b/src/pages/formSigPage/FormSigPageSearchForm.js @@ -13,7 +13,7 @@ import { LIONER_BUTTON_THEME } from "../../themes/colorConst"; import { CARD_MAX_WIDTH } from "../../themes/themeConst"; const FORM_CODES = ['IDA', 'FNA', 'HSBCFIN', 'HSBCA31', 'MLB03S', 'MLFNA_EN', 'MLFNA_CHI', 'SLFNA_EN', 'SLFNA_CHI', 'SLAPP', 'SLGII']; -const SIG_TYPES = ['upload1', 'upload2']; +const SIG_TYPES = ['upload1', 'upload2', 'upload3']; const FormSigPageSearchForm = ({ applySearch }) => { const { reset, register, handleSubmit } = useForm(); diff --git a/src/pages/formSigPage/FormSigPageTable.js b/src/pages/formSigPage/FormSigPageTable.js index bd72905..461d338 100644 --- a/src/pages/formSigPage/FormSigPageTable.js +++ b/src/pages/formSigPage/FormSigPageTable.js @@ -37,8 +37,7 @@ import { LIONER_BUTTON_THEME } from "../../themes/colorConst"; import { ThemeProvider } from "@emotion/react"; const FORM_CODES = ['IDA', 'FNA', 'HSBCFIN', 'HSBCA31', 'MLB03S', 'MLFNA_EN', 'MLFNA_CHI', 'SLFNA_EN', 'SLFNA_CHI', 'SLAPP', 'SLGII']; -const SIG_TYPES = ['upload1', 'upload2']; -const ACTIONS = ['REPLACE', 'SKIP_AND_APPEND']; +const SIG_TYPES = ['upload1', 'upload2', 'upload3']; let newRowId = -1; @@ -86,7 +85,6 @@ function EditToolbar({ setRows, setRowModesModel }) { sigType: 'upload1', pageFrom: 1, pageTo: 1, - action: 'REPLACE', isNew: true, }, ...oldRows, @@ -178,13 +176,12 @@ export default function FormSigPageTable({ recordList }) { sigType: newRow.sigType, pageFrom: Number(newRow.pageFrom), pageTo: Number(newRow.pageTo), - action: newRow.action, }; if (!isCreate) payload.id = Number(newRow.id); return new Promise((resolve, reject) => { if (!payload.formCode || !payload.startDate || !payload.sigType || - payload.pageFrom == null || payload.pageTo == null || !payload.action) { + payload.pageFrom == null || payload.pageTo == null) { reject(new Error('All fields are required')); return; } @@ -280,14 +277,6 @@ export default function FormSigPageTable({ recordList }) { }, { field: 'pageFrom', headerName: 'Page From', type: 'number', width: 95, editable: true }, { field: 'pageTo', headerName: 'Page To', type: 'number', width: 95, editable: true }, - { - field: 'action', - headerName: 'Action', - width: 140, - editable: true, - type: 'singleSelect', - valueOptions: ACTIONS, - }, ]; return ( diff --git a/src/pages/lionerUserSearchPage/UserSearchForm.js b/src/pages/lionerUserSearchPage/UserSearchForm.js index 7781635..c57f1f5 100644 --- a/src/pages/lionerUserSearchPage/UserSearchForm.js +++ b/src/pages/lionerUserSearchPage/UserSearchForm.js @@ -1,13 +1,14 @@ // material-ui import { Button, - Grid, + Grid, TextField, Typography, Dialog, DialogTitle, DialogContent, - DialogActions + DialogActions, + InputLabel, } from '@mui/material'; import MainCard from "../../components/MainCard"; import {useForm} from "react-hook-form"; @@ -21,6 +22,7 @@ import {ThemeProvider} from "@emotion/react"; import {LIONER_BUTTON_THEME} from "../../themes/colorConst"; import {USER_GROUP_COMBO} from "../../utils/ComboConst"; import {CARD_MAX_WIDTH} from "../../themes/themeConst"; +import {GET_CONSULTANT_COMBO_LIST} from "../../utils/ApiPathConst"; // ==============================|| USER SEARCH FORM ||============================== // @@ -33,9 +35,22 @@ const UserSearchForm = ({applySearch}) => { // States for Report Dialog const [openReportDialog, setOpenReportDialog] = useState(false); const [reportDates, setReportDates] = useState({ fromDate: '', toDate: '' }); + const [consultantOptions, setConsultantOptions] = useState([]); + const [selectedConsultants, setSelectedConsultants] = useState([]); const { reset, register, handleSubmit } = useForm(); + useEffect(() => { + axios + .get(`${apiPath}${GET_CONSULTANT_COMBO_LIST}`) + .then((res) => { + if (res.data?.records) { + setConsultantOptions(res.data.records); + } + }) + .catch(() => {}); + }, []); + const onSubmit = (data) => { const temp = { username: data.userName, @@ -53,11 +68,26 @@ const UserSearchForm = ({applySearch}) => { const handleCloseReport = () => { setOpenReportDialog(false); setReportDates({ fromDate: '', toDate: '' }); + setSelectedConsultants([]); }; const handleExport = () => { + const params = { + fromDate: reportDates.fromDate, + toDate: reportDates.toDate, + }; + if (selectedConsultants.length > 0) { + params.consultantIds = selectedConsultants.map((c) => c.id); + } axios.get(`${apiPath}/client/excel-client-consultant-report`, { - params: reportDates, + params, + paramsSerializer: (p) => { + const search = new URLSearchParams(); + if (p.fromDate) search.append('fromDate', p.fromDate); + if (p.toDate) search.append('toDate', p.toDate); + (p.consultantIds || []).forEach((id) => search.append('consultantIds', String(id))); + return search.toString(); + }, responseType: 'blob', }) .then((response) => { @@ -153,7 +183,7 @@ const UserSearchForm = ({applySearch}) => { {/* Report Criteria Dialog */} - + Export Client Consultant Report @@ -177,6 +207,27 @@ const UserSearchForm = ({applySearch}) => { onChange={(e) => setReportDates({ ...reportDates, toDate: e.target.value })} /> + + Consultant (optional) + setSelectedConsultants(newValue)} + getOptionLabel={(option) => option?.name ?? ''} + isOptionEqualToValue={(a, b) => a?.id === b?.id} + renderInput={(params) => ( + + )} + /> + diff --git a/src/pages/pdf/PdfSearchPage/PdfTable.js b/src/pages/pdf/PdfSearchPage/PdfTable.js index e61252d..52be4ed 100644 --- a/src/pages/pdf/PdfSearchPage/PdfTable.js +++ b/src/pages/pdf/PdfSearchPage/PdfTable.js @@ -39,7 +39,22 @@ import {ThemeProvider} from "@emotion/react"; const initialUploadState = { id: null, templateName: null, // This will be the formCode - refType: null, // To differentiate between upload1 and upload2 if needed + refType: null, // upload1 | upload2 | upload3 +} + +const REF_TYPE_TO_FILE_FIELD = { + upload1: "upload1FileId", + upload2: "upload2FileId", + upload3: "upload3FileId", +}; + +/** Backend adds sigTypes from form_sig_page for this row's formCode + created date. */ +function normalizeSigTypes(row) { + const st = row?.sigTypes; + if (Array.isArray(st) && st.length > 0) { + return st; + } + return ["upload1"]; } export default function PdfTable({recordList}) { @@ -73,13 +88,19 @@ export default function PdfTable({recordList}) { navigate(`/pdf/maintain/${id}`); }; - /** Safely get YYYY-MM-DD from row.created; returns null if missing or invalid. */ + /** + * YYYY-MM-DD in local calendar (matches server JVM date for filled_form.created better than UTC ISO). + * Used only as fallback when the API cannot resolve filledFormId. + */ const getAsOfDateString = (row) => { const raw = row?.created; if (raw == null || raw === "") return null; const d = new Date(raw); if (Number.isNaN(d.getTime())) return null; - return d.toISOString().slice(0, 10); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; }; /** @@ -97,6 +118,7 @@ export default function PdfTable({recordList}) { setIsDialogOpen(true); const asOfDate = getAsOfDateString(row); const params = new URLSearchParams({ formCode }); + params.set("filledFormId", String(id)); if (asOfDate) params.set("asOfDate", asOfDate); axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`) .then((res) => { @@ -122,6 +144,7 @@ export default function PdfTable({recordList}) { setIsDialogOpen(true); const asOfDate = getAsOfDateString(row); const params = new URLSearchParams({ formCode }); + params.set("filledFormId", String(id)); if (asOfDate) params.set("asOfDate", asOfDate); axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`) .then((res) => { @@ -132,6 +155,28 @@ export default function PdfTable({recordList}) { .catch(() => setCurrentUploadLabel("Upload 2")); }; + const handleUpload3Click = (id, templateName, formCode, row) => () => { + setCurrentUploadRow({ + id: id, + templateName: templateName, + formCode: formCode, + refType: "upload3" + }); + setCurrentUploadLabel(null); + setIsDialogOpen(true); + const asOfDate = getAsOfDateString(row); + const params = new URLSearchParams({ formCode }); + params.set("filledFormId", String(id)); + if (asOfDate) params.set("asOfDate", asOfDate); + axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`) + .then((res) => { + const configs = res.data?.configs || []; + const upload3 = configs.find((c) => c.sigType === "upload3"); + setCurrentUploadLabel(upload3?.label || "Upload 3"); + }) + .catch(() => setCurrentUploadLabel("Upload 3")); + }; + /** * Handles the standard download (fillable file, endpoint: /download-ff/{id}) */ @@ -241,6 +286,7 @@ export default function PdfTable({recordList}) { const getUploadDialogTitle = (formCode, refType) => { if (refType === "upload1") return "Upload Signature"; if (refType === "upload2") return "Upload 2"; + if (refType === "upload3") return "Upload 3"; return "Upload File"; }; @@ -289,7 +335,7 @@ export default function PdfTable({recordList}) { prevRows.map(row => { if (row.id === id) { // Update the relevant file ID field based on refType - const updateField = refType === 'upload1' ? 'upload1FileId' : 'upload2FileId'; + const updateField = REF_TYPE_TO_FILE_FIELD[refType] || "upload1FileId"; return { ...row, [updateField]: uploadedFileId // Set the file ID to trigger the icon @@ -367,46 +413,47 @@ export default function PdfTable({recordList}) { Upload Sig. ), - width: 100, + width: 150, cellClassName: 'actions', getActions: ({ id, row }) => { // Check upload status const isUploaded1 = !!row.upload1FileId; - const isUploaded2 = !!row.upload2FileId; + const isUploaded2 = !!row.upload2FileId; + const isUploaded3 = !!row.upload3FileId; const templateName = row.templateName; const formCode = row.formCode; - // FIX 5: Use conditional colors for Upload 1 - const upload1Icon = isUploaded1 - ? // Green tick if uploaded - : ; // Warning color if not uploaded + const sigTypes = normalizeSigTypes(row); + const showUpload1 = sigTypes.includes("upload1"); + const showUpload2 = sigTypes.includes("upload2"); + const showUpload3 = sigTypes.includes("upload3"); - const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature"; + const actions = []; - // Define the actions - const actions = [ - - - - ]; + if (showUpload1) { + const upload1Icon = isUploaded1 + ? + : ; + const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature"; + actions.push( + + + + ); + } - // Conditional rendering logic for Upload 2 - if (row.formCode === "MLB03S" || row.formCode === "SLGII" || row.formCode === "SLAPP") { - - // FIX 5: Use conditional colors for Upload 2 + if (showUpload2) { const upload2Icon = isUploaded2 - ? // Green tick if uploaded - : ; // Warning color if not uploaded - + ? + : ; const upload2Label = isUploaded2 ? "Update 2" : "Upload 2"; - actions.push( + : ; + const upload3Label = isUploaded3 ? "Update 3" : "Upload 3"; + actions.push( + + + + ); + } + return actions; }, }, diff --git a/src/routes/ClientRoutes.js b/src/routes/ClientRoutes.js index 4c093d0..bba85bd 100644 --- a/src/routes/ClientRoutes.js +++ b/src/routes/ClientRoutes.js @@ -16,6 +16,7 @@ const PdfFormUpAndDown = Loadable(lazy(() => import('pages/pdf/PdfFormUpAndDown' const PdfViewer = Loadable(lazy(() => import('pages/pdf/PdfViewer'))); const PdfSearchPage = Loadable(lazy(() => import('pages/pdf/PdfSearchPage'))); const TemplateSearchPage = Loadable(lazy(() => import('pages/pdf/TemplateSearchPage'))); +const FormSigPageSearchPanel = Loadable(lazy(() => import('pages/formSigPage'))); // ==============================|| AUTH ROUTING ||============================== // @@ -88,6 +89,18 @@ const ClientRoutes =() => { ), }, + { + path: '/formSigPage', + element: ( + + {handleRouteAbility( + ability.can('VIEW', 'TEMPLATE'), + , + + )} + + ), + }, { path: '/pdf/form-up-down/:id', element: ( diff --git a/src/routes/SettingRoutes.js b/src/routes/SettingRoutes.js index 56bf7b8..e81bf70 100644 --- a/src/routes/SettingRoutes.js +++ b/src/routes/SettingRoutes.js @@ -29,8 +29,6 @@ const EmailConfigPage = Loadable(lazy(() => import('pages/lionerEmailConfig'))); const GenerateReminderPage = Loadable(lazy(() => import('pages/lionerManualButtonPage'))); const ClientDepartmentPage = Loadable(lazy(() => import('pages/lionerClientDepartmentPage'))); const ProfilePage = Loadable(lazy(() => import('pages/profile/profile'))); -const FormSigPageSearchPanel = Loadable(lazy(() => import('pages/formSigPage'))); - // ==============================|| AUTH ROUTING ||============================== // const SettingRoutes = () => { @@ -261,16 +259,6 @@ const SettingRoutes = () => { ) ), }, - { - path: 'formSigPage', - element: ( - handleRouteAbility( - ability.can('MANAGE', 'SYSTEM_CONFIGURATION'), - , - - ) - ), - }, { path: 'logout', element: