| @@ -13,7 +13,8 @@ import { | |||||
| MenuUnfoldOutlined, | MenuUnfoldOutlined, | ||||
| FileSearchOutlined, | FileSearchOutlined, | ||||
| MailOutlined, | MailOutlined, | ||||
| ApartmentOutlined | |||||
| ApartmentOutlined, | |||||
| FilePdfOutlined | |||||
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| // icons | // icons | ||||
| @@ -31,7 +32,8 @@ const icons = { | |||||
| MenuUnfoldOutlined, | MenuUnfoldOutlined, | ||||
| FileSearchOutlined, | FileSearchOutlined, | ||||
| MailOutlined, | MailOutlined, | ||||
| ApartmentOutlined | |||||
| ApartmentOutlined, | |||||
| FilePdfOutlined | |||||
| }; | }; | ||||
| // ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // | // ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // | ||||
| @@ -141,6 +143,15 @@ const setting = { | |||||
| breadcrumbs: false, | breadcrumbs: false, | ||||
| ability:['VIEW','USER'] | ability:['VIEW','USER'] | ||||
| }, | }, | ||||
| { | |||||
| id: 'formSigPage', | |||||
| title: 'Form Sig Page', | |||||
| type: 'item', | |||||
| url: '/formSigPage', | |||||
| icon: icons.FilePdfOutlined, | |||||
| breadcrumbs: false, | |||||
| ability:['MANAGE','SYSTEM_CONFIGURATION'] | |||||
| }, | |||||
| // { | // { | ||||
| // id: 'passwordPolicy', | // id: 'passwordPolicy', | ||||
| // title: 'Password Policy', | // title: 'Password Policy', | ||||
| @@ -0,0 +1,84 @@ | |||||
| import { | |||||
| Button, | |||||
| Grid, | |||||
| InputLabel, | |||||
| MenuItem, | |||||
| TextField, | |||||
| } from '@mui/material'; | |||||
| import MainCard from "../../components/MainCard"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import * as React from "react"; | |||||
| import { ThemeProvider } from "@emotion/react"; | |||||
| 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 FormSigPageSearchForm = ({ applySearch }) => { | |||||
| const { reset, register, handleSubmit } = useForm(); | |||||
| const onSubmit = (data) => { | |||||
| applySearch(data); | |||||
| }; | |||||
| return ( | |||||
| <MainCard | |||||
| xs={12} | |||||
| md={12} | |||||
| lg={12} | |||||
| content={false} | |||||
| sx={{ width: CARD_MAX_WIDTH }} | |||||
| > | |||||
| <form onSubmit={handleSubmit(onSubmit)}> | |||||
| <Grid sx={{ mb: 2 }} /> | |||||
| <Grid container alignItems="center" spacing={2}> | |||||
| <Grid item xs={12} sm={6} md={4} lg={3} sx={{ ml: 3, mr: 3 }}> | |||||
| <InputLabel>Form Code</InputLabel> | |||||
| <TextField | |||||
| fullWidth | |||||
| select | |||||
| {...register("formCode")} | |||||
| id="formCode" | |||||
| > | |||||
| <MenuItem value="">All</MenuItem> | |||||
| {FORM_CODES.map((c) => ( | |||||
| <MenuItem key={c} value={c}>{c}</MenuItem> | |||||
| ))} | |||||
| </TextField> | |||||
| </Grid> | |||||
| <Grid item xs={12} sm={6} md={4} lg={3}> | |||||
| <InputLabel>Sig Type</InputLabel> | |||||
| <TextField | |||||
| fullWidth | |||||
| select | |||||
| {...register("sigType")} | |||||
| id="sigType" | |||||
| > | |||||
| <MenuItem value="">All</MenuItem> | |||||
| {SIG_TYPES.map((t) => ( | |||||
| <MenuItem key={t} value={t}>{t}</MenuItem> | |||||
| ))} | |||||
| </TextField> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid container maxWidth justifyContent="flex-start" sx={{ mt: 2, mb: 2 }}> | |||||
| <ThemeProvider theme={LIONER_BUTTON_THEME}> | |||||
| <Grid item sx={{ ml: 3, mr: 1.5 }}> | |||||
| <Button variant="contained" type="submit" color="save"> | |||||
| Search | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item> | |||||
| <Button variant="outlined" type="button" onClick={() => { reset(); applySearch({}); }}> | |||||
| Reset | |||||
| </Button> | |||||
| </Grid> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| </form> | |||||
| </MainCard> | |||||
| ); | |||||
| }; | |||||
| export default FormSigPageSearchForm; | |||||
| @@ -0,0 +1,325 @@ | |||||
| import * as React from 'react'; | |||||
| import { | |||||
| Button, | |||||
| } from '@mui/material'; | |||||
| import { | |||||
| DataGrid, | |||||
| GridActionsCellItem, | |||||
| GridRowEditStopReasons, | |||||
| GridRowModes, | |||||
| GridToolbarContainer | |||||
| } from "@mui/x-data-grid"; | |||||
| import { DatePicker } from '@mui/x-date-pickers/DatePicker'; | |||||
| import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; | |||||
| import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; | |||||
| import dayjs from 'dayjs'; | |||||
| import EditIcon from '@mui/icons-material/Edit'; | |||||
| import DeleteIcon from '@mui/icons-material/DeleteOutlined'; | |||||
| import SaveIcon from '@mui/icons-material/Save'; | |||||
| import CancelIcon from '@mui/icons-material/Close'; | |||||
| import AddIcon from '@mui/icons-material/Add'; | |||||
| import axios from "axios"; | |||||
| import { apiPath } from "../../auth/utils"; | |||||
| import { | |||||
| GET_FORM_SIG_PAGE_LIST, | |||||
| UPDATE_FORM_SIG_PAGE_PATH, | |||||
| } from "../../utils/ApiPathConst"; | |||||
| import { | |||||
| CustomNoRowsOverlay, | |||||
| GeneralConfirmWindow, | |||||
| notifyDeleteError, | |||||
| notifyDeleteSuccess, | |||||
| notifySaveSuccess, | |||||
| removeObjectWithId | |||||
| } from "../../utils/CommonFunction"; | |||||
| import UploadContext from "../../components/UploadProvider"; | |||||
| 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']; | |||||
| let newRowId = -1; | |||||
| function normalizeStartDate(value) { | |||||
| if (value == null || value === '') return ''; | |||||
| if (dayjs.isDayjs(value)) return value.format('YYYY-MM-DD'); | |||||
| const d = dayjs(value); | |||||
| return d.isValid() ? d.format('YYYY-MM-DD') : String(value); | |||||
| } | |||||
| function toDayjsOrNull(value) { | |||||
| if (value == null || value === '') return null; | |||||
| if (dayjs.isDayjs(value)) return value; | |||||
| if (typeof value === 'string') { | |||||
| const d = dayjs(value); | |||||
| return d.isValid() ? d : null; | |||||
| } | |||||
| if (Array.isArray(value) && value.length >= 3) { | |||||
| const y = value[0]; | |||||
| const m = String(value[1]).padStart(2, '0'); | |||||
| const d = dayjs(`${y}-${m}-${String(value[2]).padStart(2, '0')}`); | |||||
| return d.isValid() ? d : null; | |||||
| } | |||||
| if (typeof value === 'object' && value.year != null && value.month != null && value.day != null) { | |||||
| const d = dayjs(`${value.year}-${String(value.month).padStart(2, '0')}-${String(value.day).padStart(2, '0')}`); | |||||
| return d.isValid() ? d : null; | |||||
| } | |||||
| const d = dayjs(value); | |||||
| return d.isValid() ? d : null; | |||||
| } | |||||
| function formatStartDate(value) { | |||||
| const d = toDayjsOrNull(value); | |||||
| return d ? d.format('YYYY-MM-DD') : ''; | |||||
| } | |||||
| function EditToolbar({ setRows, setRowModesModel }) { | |||||
| const handleClick = () => { | |||||
| const id = newRowId--; | |||||
| setRows((oldRows) => [ | |||||
| { | |||||
| id, | |||||
| formCode: 'IDA', | |||||
| startDate: '2000-01-01', | |||||
| sigType: 'upload1', | |||||
| pageFrom: 1, | |||||
| pageTo: 1, | |||||
| action: 'REPLACE', | |||||
| isNew: true, | |||||
| }, | |||||
| ...oldRows, | |||||
| ]); | |||||
| setRowModesModel((oldModel) => ({ | |||||
| ...oldModel, | |||||
| [id]: { mode: GridRowModes.Edit, fieldToFocus: 'formCode' }, | |||||
| })); | |||||
| }; | |||||
| return ( | |||||
| <GridToolbarContainer sx={{ ml: 1 }}> | |||||
| <Button color="primary" startIcon={<AddIcon />} onClick={handleClick}> | |||||
| Add Form Sig Page | |||||
| </Button> | |||||
| </GridToolbarContainer> | |||||
| ); | |||||
| } | |||||
| export default function FormSigPageTable({ recordList }) { | |||||
| const [rows, setRows] = React.useState([]); | |||||
| const [rowModesModel, setRowModesModel] = React.useState({}); | |||||
| const { setIsUploading } = React.useContext(UploadContext); | |||||
| const [isWindowOpen, setIsWindowOpen] = React.useState(false); | |||||
| const [selectedId, setSelectedId] = React.useState(null); | |||||
| const [paginationModel, setPaginationModel] = React.useState({ page: 0, pageSize: 10 }); | |||||
| React.useEffect(() => { | |||||
| setPaginationModel({ page: 0, pageSize: 10 }); | |||||
| setRows(recordList || []); | |||||
| }, [recordList]); | |||||
| const handleClose = () => setIsWindowOpen(false); | |||||
| const handleDeleteClick = (id) => () => { | |||||
| setIsWindowOpen(true); | |||||
| setSelectedId(id); | |||||
| }; | |||||
| const updateData = () => { | |||||
| setIsUploading(true); | |||||
| axios | |||||
| .delete(`${apiPath}${GET_FORM_SIG_PAGE_LIST}/${selectedId}`) | |||||
| .then((response) => { | |||||
| if (response.status === 204) { | |||||
| notifyDeleteSuccess(); | |||||
| setRows((prev) => removeObjectWithId(prev, selectedId)); | |||||
| setIsWindowOpen(false); | |||||
| } | |||||
| setIsUploading(false); | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.error(err); | |||||
| notifyDeleteError(err?.response?.data?.message || 'Delete failed'); | |||||
| setIsUploading(false); | |||||
| }); | |||||
| }; | |||||
| const handleRowEditStop = (params, event) => { | |||||
| if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||||
| event.defaultMuiPrevented = true; | |||||
| } | |||||
| }; | |||||
| const handleEditClick = (id) => () => { | |||||
| setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.Edit, fieldToFocus: 'formCode' } })); | |||||
| }; | |||||
| const handleSaveClick = (id) => () => { | |||||
| setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View } })); | |||||
| }; | |||||
| const handleCancelClick = (id) => () => { | |||||
| setRowModesModel((prev => { | |||||
| const next = { ...prev, [id]: { mode: GridRowModes.View, ignoreModifications: true } }; | |||||
| return next; | |||||
| })); | |||||
| const editedRow = rows.find((r) => r.id === id); | |||||
| if (editedRow?.isNew) { | |||||
| setRows((prev) => prev.filter((r) => r.id !== id)); | |||||
| } | |||||
| }; | |||||
| const processRowUpdate = (newRow) => { | |||||
| const isCreate = newRow.isNew === true || newRow.id == null || Number(newRow.id) <= 0; | |||||
| const payload = { | |||||
| formCode: newRow.formCode, | |||||
| startDate: normalizeStartDate(newRow.startDate), | |||||
| 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) { | |||||
| reject(new Error('All fields are required')); | |||||
| return; | |||||
| } | |||||
| if (payload.pageFrom > payload.pageTo) { | |||||
| reject(new Error('pageFrom must be <= pageTo')); | |||||
| return; | |||||
| } | |||||
| setIsUploading(true); | |||||
| axios | |||||
| .post(`${apiPath}${UPDATE_FORM_SIG_PAGE_PATH}`, payload) | |||||
| .then((res) => { | |||||
| const savedId = res.data?.id; | |||||
| const updatedRow = { ...newRow, id: savedId != null ? savedId : newRow.id, isNew: false }; | |||||
| setRows((prev) => prev.map((r) => (r.id === newRow.id ? updatedRow : r))); | |||||
| notifySaveSuccess(); | |||||
| setIsUploading(false); | |||||
| resolve(updatedRow); | |||||
| }) | |||||
| .catch((err) => { | |||||
| setIsUploading(false); | |||||
| notifyDeleteError(err?.response?.data?.message || 'Save failed'); | |||||
| reject(err); | |||||
| }); | |||||
| }); | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| field: 'actions', | |||||
| type: 'actions', | |||||
| headerName: 'Actions', | |||||
| width: 120, | |||||
| getActions: ({ id }) => { | |||||
| const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; | |||||
| if (isInEditMode) { | |||||
| return [ | |||||
| <ThemeProvider key="cancel" theme={LIONER_BUTTON_THEME}> | |||||
| <GridActionsCellItem icon={<CancelIcon />} label="Cancel" onClick={handleCancelClick(id)} color="delete" />, | |||||
| </ThemeProvider>, | |||||
| <ThemeProvider key="save" theme={LIONER_BUTTON_THEME}> | |||||
| <GridActionsCellItem icon={<SaveIcon />} label="Save" onClick={handleSaveClick(id)} color="save" />, | |||||
| </ThemeProvider>, | |||||
| ]; | |||||
| } | |||||
| return [ | |||||
| <ThemeProvider key="delete" theme={LIONER_BUTTON_THEME}> | |||||
| <GridActionsCellItem icon={<DeleteIcon />} label="Delete" onClick={handleDeleteClick(id)} color="delete" />, | |||||
| </ThemeProvider>, | |||||
| <ThemeProvider key="edit" theme={LIONER_BUTTON_THEME}> | |||||
| <GridActionsCellItem icon={<EditIcon />} label="Edit" onClick={handleEditClick(id)} color="edit" />, | |||||
| </ThemeProvider>, | |||||
| ]; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: 'formCode', | |||||
| headerName: 'Form Code', | |||||
| width: 120, | |||||
| editable: true, | |||||
| type: 'singleSelect', | |||||
| valueOptions: FORM_CODES, | |||||
| }, | |||||
| { | |||||
| field: 'startDate', | |||||
| headerName: 'Start Date', | |||||
| width: 140, | |||||
| editable: true, | |||||
| valueGetter: (params) => formatStartDate(params.row?.startDate), | |||||
| renderCell: (params) => formatStartDate(params.row?.startDate), | |||||
| renderEditCell: (params) => ( | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DatePicker | |||||
| value={toDayjsOrNull(params.row?.startDate)} | |||||
| onChange={(d) => | |||||
| params.api.setEditCellValue({ | |||||
| id: params.id, | |||||
| field: params.field, | |||||
| value: d ? d.format('YYYY-MM-DD') : '', | |||||
| }) | |||||
| } | |||||
| slotProps={{ textField: { size: 'small', fullWidth: true } }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| field: 'sigType', | |||||
| headerName: 'Sig Type', | |||||
| width: 90, | |||||
| editable: true, | |||||
| type: 'singleSelect', | |||||
| valueOptions: SIG_TYPES, | |||||
| }, | |||||
| { 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 ( | |||||
| <div style={{ width: '100%' }}> | |||||
| <DataGrid | |||||
| rows={rows} | |||||
| columns={columns} | |||||
| columnHeaderHeight={45} | |||||
| editMode="row" | |||||
| rowModesModel={rowModesModel} | |||||
| onRowModesModelChange={setRowModesModel} | |||||
| onRowEditStop={handleRowEditStop} | |||||
| processRowUpdate={processRowUpdate} | |||||
| onProcessRowUpdateError={(err) => notifyDeleteError(err?.message || 'Update failed')} | |||||
| getRowHeight={() => 'auto'} | |||||
| paginationModel={paginationModel} | |||||
| onPaginationModelChange={setPaginationModel} | |||||
| pageSizeOptions={[10, 25, 50]} | |||||
| slots={{ | |||||
| toolbar: EditToolbar, | |||||
| noRowsOverlay: () => CustomNoRowsOverlay(), | |||||
| }} | |||||
| slotProps={{ toolbar: { setRows, setRowModesModel } }} | |||||
| autoHeight | |||||
| /> | |||||
| <GeneralConfirmWindow | |||||
| isWindowOpen={isWindowOpen} | |||||
| title="Delete Form Sig Page" | |||||
| content="Are you sure you want to delete this record?" | |||||
| onNormalClose={handleClose} | |||||
| onConfirmClose={updateData} | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,78 @@ | |||||
| import { | |||||
| Box, | |||||
| Grid, | |||||
| Typography | |||||
| } from '@mui/material'; | |||||
| import MainCard from "../../components/MainCard"; | |||||
| import FormSigPageSearchForm from "./FormSigPageSearchForm"; | |||||
| import FormSigPageTable from "./FormSigPageTable"; | |||||
| import { useEffect, useState } from "react"; | |||||
| import * as React from "react"; | |||||
| import axios from "axios"; | |||||
| import { apiPath } from "../../auth/utils"; | |||||
| import { GET_FORM_SIG_PAGE_LIST } from "../../utils/ApiPathConst"; | |||||
| import LoadingComponent from "../extra-pages/LoadingComponent"; | |||||
| import { LIONER_FORM_THEME, CARD_MAX_WIDTH } from "../../themes/themeConst"; | |||||
| import { ThemeProvider } from "@emotion/react"; | |||||
| const FormSigPageSearchPanel = () => { | |||||
| const [record, setRecord] = useState([]); | |||||
| const [searchCriteria, setSearchCriteria] = useState({}); | |||||
| const [onReady, setOnReady] = useState(false); | |||||
| const fetchList = () => { | |||||
| const params = {}; | |||||
| if (searchCriteria.formCode) params.formCode = searchCriteria.formCode; | |||||
| if (searchCriteria.sigType) params.sigType = searchCriteria.sigType; | |||||
| axios | |||||
| .get(`${apiPath}${GET_FORM_SIG_PAGE_LIST}`, { params }) | |||||
| .then((response) => { | |||||
| if (response.status === 200 && response.data?.records) { | |||||
| setRecord(response.data.records); | |||||
| } | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.error(err); | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| fetchList(); | |||||
| }, [searchCriteria]); | |||||
| useEffect(() => { | |||||
| setOnReady(true); | |||||
| }, [record]); | |||||
| const applySearch = (input) => { | |||||
| setSearchCriteria(input || {}); | |||||
| }; | |||||
| if (!onReady) return <LoadingComponent />; | |||||
| return ( | |||||
| <Grid container rowSpacing={3} columnSpacing={2.75}> | |||||
| <ThemeProvider theme={LIONER_FORM_THEME}> | |||||
| <Grid item xs={12} md={12} lg={12} sx={{ mt: -1 }}> | |||||
| <Grid container justifyContent="flex-start" alignItems="center"> | |||||
| <Grid item xs={3} sx={{ mb: -2.25 }}> | |||||
| <Box sx={{ display: 'flex', alignItems: 'center' }}> | |||||
| <Typography variant="h5">Form Signature Page Config</Typography> | |||||
| </Box> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <FormSigPageSearchForm applySearch={applySearch} /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={12} lg={12} sx={{ mt: { lg: -1.5 } }}> | |||||
| <MainCard elevation={0} content={false} sx={{ width: CARD_MAX_WIDTH }}> | |||||
| <FormSigPageTable recordList={record} /> | |||||
| </MainCard> | |||||
| </Grid> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default FormSigPageSearchPanel; | |||||
| @@ -1,7 +1,7 @@ | |||||
| // material-ui | // material-ui | ||||
| import * as React from 'react'; | import * as React from 'react'; | ||||
| import {apiPath} from "../../../auth/utils"; | import {apiPath} from "../../../auth/utils"; | ||||
| import { POST_SIG_UPLOAD1 } from "../../../utils/ApiPathConst"; | |||||
| import { POST_SIG_UPLOAD1, GET_FORM_SIG_PAGE_CONFIG } from "../../../utils/ApiPathConst"; | |||||
| import axios from 'axios'; | import axios from 'axios'; | ||||
| import { | import { | ||||
| DataGrid, | DataGrid, | ||||
| @@ -50,6 +50,7 @@ export default function PdfTable({recordList}) { | |||||
| const [isDialogOpen, setIsDialogOpen] = React.useState(false); | const [isDialogOpen, setIsDialogOpen] = React.useState(false); | ||||
| // State to hold the ID, templateName, and refType for the current upload operation | // State to hold the ID, templateName, and refType for the current upload operation | ||||
| const [currentUploadRow, setCurrentUploadRow] = React.useState(initialUploadState); | const [currentUploadRow, setCurrentUploadRow] = React.useState(initialUploadState); | ||||
| const [currentUploadLabel, setCurrentUploadLabel] = React.useState(null); | |||||
| const [isUploading, setIsUploading] = React.useState(false); | const [isUploading, setIsUploading] = React.useState(false); | ||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| @@ -72,32 +73,63 @@ export default function PdfTable({recordList}) { | |||||
| navigate(`/pdf/maintain/${id}`); | navigate(`/pdf/maintain/${id}`); | ||||
| }; | }; | ||||
| /** Safely get YYYY-MM-DD from row.created; returns null if missing or invalid. */ | |||||
| 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); | |||||
| }; | |||||
| /** | /** | ||||
| * Opens the upload dialog and sets the current row details for Upload 1 | |||||
| * Opens the upload dialog and sets the current row details for Upload 1. | |||||
| * Fetches form-sig-page config from API for dynamic label (page number). | |||||
| */ | */ | ||||
| const handleUploadClick = (id, templateName, formCode) => () => { | |||||
| const handleUploadClick = (id, templateName, formCode, row) => () => { | |||||
| setCurrentUploadRow({ | setCurrentUploadRow({ | ||||
| id: id, | id: id, | ||||
| templateName: templateName, | templateName: templateName, | ||||
| formCode: formCode, | formCode: formCode, | ||||
| refType: "upload1" | refType: "upload1" | ||||
| }); | }); | ||||
| setCurrentUploadLabel(null); | |||||
| setIsDialogOpen(true); | setIsDialogOpen(true); | ||||
| const asOfDate = getAsOfDateString(row); | |||||
| const params = new URLSearchParams({ formCode }); | |||||
| if (asOfDate) params.set("asOfDate", asOfDate); | |||||
| axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`) | |||||
| .then((res) => { | |||||
| const configs = res.data?.configs || []; | |||||
| const upload1 = configs.find((c) => c.sigType === "upload1"); | |||||
| setCurrentUploadLabel(upload1?.label || "Upload Signature"); | |||||
| }) | |||||
| .catch(() => setCurrentUploadLabel("Upload Signature")); | |||||
| }; | }; | ||||
| /** | /** | ||||
| * Opens the upload dialog and sets the current row details for Upload 2 | |||||
| * Opens the upload dialog and sets the current row details for Upload 2. | |||||
| * Fetches form-sig-page config from API for dynamic label. | |||||
| */ | */ | ||||
| const handleUpload2Click = (id, templateName, formCode) => () => { | |||||
| // Placeholder for Upload 2 | |||||
| console.log(`Uploading for row ID ${id} (Upload 2)`); | |||||
| setCurrentUploadRow({ | |||||
| const handleUpload2Click = (id, templateName, formCode, row) => () => { | |||||
| setCurrentUploadRow({ | |||||
| id: id, | id: id, | ||||
| templateName: templateName, | templateName: templateName, | ||||
| formCode: formCode, | formCode: formCode, | ||||
| refType: "upload2" // A different refType for a different upload logic/API | |||||
| refType: "upload2" | |||||
| }); | }); | ||||
| setIsDialogOpen(true); | |||||
| setCurrentUploadLabel(null); | |||||
| setIsDialogOpen(true); | |||||
| const asOfDate = getAsOfDateString(row); | |||||
| const params = new URLSearchParams({ formCode }); | |||||
| if (asOfDate) params.set("asOfDate", asOfDate); | |||||
| axios.get(`${apiPath}${GET_FORM_SIG_PAGE_CONFIG}?${params}`) | |||||
| .then((res) => { | |||||
| const configs = res.data?.configs || []; | |||||
| const upload2 = configs.find((c) => c.sigType === "upload2"); | |||||
| setCurrentUploadLabel(upload2?.label || "Upload 2"); | |||||
| }) | |||||
| .catch(() => setCurrentUploadLabel("Upload 2")); | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -198,60 +230,18 @@ export default function PdfTable({recordList}) { | |||||
| const handleCloseDialog = () => { | const handleCloseDialog = () => { | ||||
| setIsDialogOpen(false); | setIsDialogOpen(false); | ||||
| setCurrentUploadRow(initialUploadState); // Reset the current row | |||||
| setCurrentUploadRow(initialUploadState); | |||||
| setCurrentUploadLabel(null); | |||||
| if (fileInputRef.current) { | if (fileInputRef.current) { | ||||
| fileInputRef.current.value = ""; // Clear the file input | fileInputRef.current.value = ""; // Clear the file input | ||||
| } | } | ||||
| }; | }; | ||||
| // Function to generate the dynamic dialog title | |||||
| // Fallback dialog title when API config is not yet loaded or fails | |||||
| const getUploadDialogTitle = (formCode, refType) => { | const getUploadDialogTitle = (formCode, refType) => { | ||||
| console.log("formCode:" + formCode + " refType:" + refType); | |||||
| if (refType === 'upload1') { | |||||
| switch (formCode) { | |||||
| case "IDA": | |||||
| return "Upload Page 15"; | |||||
| case "FNA": | |||||
| return "Upload Page 10"; | |||||
| case "HSBCFIN": | |||||
| return "Upload Page 11"; | |||||
| case "HSBCA31": | |||||
| return "Upload Page 28-29"; | |||||
| case "MLB03S": | |||||
| return "Upload Page 9"; | |||||
| case "MLFNA_EN": | |||||
| return "Upload Page 4"; | |||||
| case "MLFNA_CHI": | |||||
| return "Upload Page 4"; | |||||
| case "SLGII": | |||||
| return "Upload Page 13"; | |||||
| case "SLAPP": | |||||
| return "Upload Page 17"; | |||||
| case "SLFNA_EN": | |||||
| return "Upload Page 5"; | |||||
| case "SLFNA_CHI": | |||||
| return "Upload Page 5"; | |||||
| default: | |||||
| return "Unknown Form"; | |||||
| } | |||||
| }else if (refType === 'upload2') { | |||||
| switch (formCode) { | |||||
| case "MLB03S": | |||||
| return "Upload Page 12-13"; | |||||
| case "SLGII": | |||||
| return "Upload Page 15-16"; | |||||
| case "SLAPP": | |||||
| return "Upload Page 19-20"; | |||||
| default: | |||||
| return "Unknown Form"; | |||||
| } | |||||
| } | |||||
| // Handle other refTypes if needed, e.g., 'upload2' | |||||
| if (refType === 'upload2') { | |||||
| return `Upload Template 2 for ${formCode}`; | |||||
| } | |||||
| return "Upload File"; // Fallback | |||||
| if (refType === "upload1") return "Upload Signature"; | |||||
| if (refType === "upload2") return "Upload 2"; | |||||
| return "Upload File"; | |||||
| }; | }; | ||||
| // Function to handle file selection and API submission | // Function to handle file selection and API submission | ||||
| @@ -401,7 +391,7 @@ export default function PdfTable({recordList}) { | |||||
| icon={upload1Icon} // Use the dynamic icon | icon={upload1Icon} // Use the dynamic icon | ||||
| label={upload1Label} // Use the dynamic label | label={upload1Label} // Use the dynamic label | ||||
| className="textPrimary" | className="textPrimary" | ||||
| onClick={handleUploadClick(id, templateName, formCode)} | |||||
| onClick={handleUploadClick(id, templateName, formCode, row)} | |||||
| color="upload" | color="upload" | ||||
| /> | /> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| @@ -423,7 +413,7 @@ export default function PdfTable({recordList}) { | |||||
| icon={upload2Icon} | icon={upload2Icon} | ||||
| label={upload2Label} | label={upload2Label} | ||||
| className="textPrimary" | className="textPrimary" | ||||
| onClick={handleUpload2Click(id, templateName, formCode)} | |||||
| onClick={handleUpload2Click(id, templateName, formCode, row)} | |||||
| color="upload" | color="upload" | ||||
| /> | /> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| @@ -551,8 +541,7 @@ export default function PdfTable({recordList}) { | |||||
| TransitionProps={{ onExited: handleCloseDialog }} | TransitionProps={{ onExited: handleCloseDialog }} | ||||
| > | > | ||||
| <DialogTitle> | <DialogTitle> | ||||
| {/* Dynamic Title based on currentUploadRow state */} | |||||
| **{getUploadDialogTitle(currentUploadRow.formCode, currentUploadRow.refType)}** | |||||
| {currentUploadLabel != null ? currentUploadLabel : getUploadDialogTitle(currentUploadRow.formCode, currentUploadRow.refType)} | |||||
| </DialogTitle> | </DialogTitle> | ||||
| <DialogContent dividers> | <DialogContent dividers> | ||||
| <Box sx={{ mt: 2, textAlign: 'center' }}> | <Box sx={{ mt: 2, textAlign: 'center' }}> | ||||
| @@ -29,6 +29,7 @@ const EmailConfigPage = Loadable(lazy(() => import('pages/lionerEmailConfig'))); | |||||
| const GenerateReminderPage = Loadable(lazy(() => import('pages/lionerManualButtonPage'))); | const GenerateReminderPage = Loadable(lazy(() => import('pages/lionerManualButtonPage'))); | ||||
| const ClientDepartmentPage = Loadable(lazy(() => import('pages/lionerClientDepartmentPage'))); | const ClientDepartmentPage = Loadable(lazy(() => import('pages/lionerClientDepartmentPage'))); | ||||
| const ProfilePage = Loadable(lazy(() => import('pages/profile/profile'))); | const ProfilePage = Loadable(lazy(() => import('pages/profile/profile'))); | ||||
| const FormSigPageSearchPanel = Loadable(lazy(() => import('pages/formSigPage'))); | |||||
| // ==============================|| AUTH ROUTING ||============================== // | // ==============================|| AUTH ROUTING ||============================== // | ||||
| @@ -260,6 +261,16 @@ const SettingRoutes = () => { | |||||
| ) | ) | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| path: 'formSigPage', | |||||
| element: ( | |||||
| handleRouteAbility( | |||||
| ability.can('MANAGE', 'SYSTEM_CONFIGURATION'), | |||||
| <FormSigPageSearchPanel />, | |||||
| <Navigate to="/" /> | |||||
| ) | |||||
| ), | |||||
| }, | |||||
| { | { | ||||
| path: 'logout', | path: 'logout', | ||||
| element: <LogoutPage /> | element: <LogoutPage /> | ||||
| @@ -72,6 +72,9 @@ export const GET_PDF_TEMPLATE_PATH = "/pdf/template" | |||||
| export const GET_MERGE_PDF = "/pdf/merge-pdf" | export const GET_MERGE_PDF = "/pdf/merge-pdf" | ||||
| export const GET_REMOVE_PDF_PASSWORD = "/pdf/remove-pdf-password" | export const GET_REMOVE_PDF_PASSWORD = "/pdf/remove-pdf-password" | ||||
| export const POST_SIG_UPLOAD1 = "/pdf/upload1" | export const POST_SIG_UPLOAD1 = "/pdf/upload1" | ||||
| export const GET_FORM_SIG_PAGE_CONFIG = "/pdf/form-sig-page-config" | |||||
| export const GET_FORM_SIG_PAGE_LIST = "/pdf/form-sig-page" | |||||
| export const UPDATE_FORM_SIG_PAGE_PATH = "/pdf/form-sig-page/save" | |||||
| export const GET_CLIENT_PATH = "/client" | export const GET_CLIENT_PATH = "/client" | ||||
| export const POST_CLIENT_PATH = "/client/save" | export const POST_CLIENT_PATH = "/client/save" | ||||
| export const GET_EVENT_PATH = "/event" | export const GET_EVENT_PATH = "/event" | ||||