From f4c2538df286ebc80f2189bf20c109ac6f209059 Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Wed, 9 Jul 2025 14:19:35 +0800 Subject: [PATCH] 0708 demo version --- src/components/AutoLogoutProvider.js | 2 +- src/menu-items/client.js | 29 +- src/menu-items/dashboard.js | 3 +- src/menu-items/index.js | 3 +- src/menu-items/setting.js | 18 +- .../client/ClientMaintainPage/ClientForm.js | 5 +- .../client/ClientSearchPage/ClientTable.js | 11 +- src/pages/pdf/PdfMaintainPage/index.js | 261 +++++++++++------- src/pages/pdf/PdfSearchPage/PdfSearchForm.js | 140 +++++++--- src/pages/pdf/PdfSearchPage/PdfTable.js | 18 +- src/pages/pdf/PdfSearchPage/index.js | 38 ++- src/routes/ClientRoutes.js | 13 +- src/routes/MainRoutes.js | 4 +- src/utils/CommonFunction.js | 66 ++++- 14 files changed, 442 insertions(+), 169 deletions(-) diff --git a/src/components/AutoLogoutProvider.js b/src/components/AutoLogoutProvider.js index 42b1638..daead1a 100644 --- a/src/components/AutoLogoutProvider.js +++ b/src/components/AutoLogoutProvider.js @@ -68,7 +68,7 @@ const AutoLogoutProvider = ({ children }) => { } = useIdleTimer({ onIdle, onActive, - timeout: 10_000, + timeout: 60_000, throttle: 500, crossTab: true, syncTimers: 200, diff --git a/src/menu-items/client.js b/src/menu-items/client.js index 085ce53..b6f44bf 100644 --- a/src/menu-items/client.js +++ b/src/menu-items/client.js @@ -1,13 +1,17 @@ // assets import BoyIcon from '@mui/icons-material/Boy'; +import AssignmentIcon from '@mui/icons-material/Assignment'; // icons -const ClientIcon = () => { - return ( -
- -
- ); + +const icons = { + ClientIcon : () => { return ( +
+ +
+ ) + }, + AssignmentIcon, }; // ==============================|| MENU ITEMS - DASHBOARD ||============================== // @@ -19,14 +23,23 @@ const client = { //ability:['SUPPRESS','REMINDER'], children: [ { - id: 'Client', + id: 'client', title: 'Client', type: 'item', url: '/client', - icon: ClientIcon, + icon: icons.ClientIcon, breadcrumbs: false, ability:['VIEW','DASHBOARD'] }, + // { + // id: 'template', + // title: 'Template', + // type: 'item', + // url: '/template', + // icon: icons.AssignmentIcon, + // breadcrumbs: false, + // ability:['VIEW','DASHBOARD'] + // }, ] }; diff --git a/src/menu-items/dashboard.js b/src/menu-items/dashboard.js index 1a96811..fa53001 100644 --- a/src/menu-items/dashboard.js +++ b/src/menu-items/dashboard.js @@ -32,7 +32,8 @@ const dashboard = { id: 'lionerdashboard', title: , type: 'item', - url: '/lionerDashboard', + url: '/client', + // url: '/lionerDashboard', icon: icons.SpeedIcon, breadcrumbs: false, ability:['VIEW','DASHBOARD'] diff --git a/src/menu-items/index.js b/src/menu-items/index.js index 1ee9465..3428155 100644 --- a/src/menu-items/index.js +++ b/src/menu-items/index.js @@ -12,7 +12,8 @@ import appreciation from "./appreciation"; // ==============================|| MENU ITEMS ||============================== // const menuItems = { - items: [dashboard, client, setting] + items: [client, setting] + // items: [dashboard, client, setting] }; // pages, utilities, support, misc diff --git a/src/menu-items/setting.js b/src/menu-items/setting.js index 90e09ec..e406c19 100644 --- a/src/menu-items/setting.js +++ b/src/menu-items/setting.js @@ -86,15 +86,15 @@ const setting = { // breadcrumbs: false, // ability:['MAINTAIN','CLIENT_DEPARTMENT'] // }, - { - id: 'userGroup', - title: 'User Group', - type: 'item', - url: '/usergroupSearchview', - icon: icons.UsergroupAddOutlined, - breadcrumbs: false, - ability:['MAINTAIN','USER_GROUP'] - }, + // { + // id: 'userGroup', + // title: 'User Group', + // type: 'item', + // url: '/usergroupSearchview', + // icon: icons.UsergroupAddOutlined, + // breadcrumbs: false, + // ability:['MAINTAIN','USER_GROUP'] + // }, { id: 'user', title: 'User', diff --git a/src/pages/client/ClientMaintainPage/ClientForm.js b/src/pages/client/ClientMaintainPage/ClientForm.js index b2d9177..847a2ea 100644 --- a/src/pages/client/ClientMaintainPage/ClientForm.js +++ b/src/pages/client/ClientMaintainPage/ClientForm.js @@ -1012,14 +1012,15 @@ const ClientForm = ({ refClientDetail, - + )} { return (
- {params.row.title ? `(${params.row.title})` : ''} {params.value} + {params.value} + {/* {params.row.title ? `(${params.row.title})` : ''} {params.value} */}
); } }, + { + id: 'title', + field: 'title', + headerName: 'Title', + flex: 1.5, + }, { id: 'joinDate', field: 'joinDate', diff --git a/src/pages/pdf/PdfMaintainPage/index.js b/src/pages/pdf/PdfMaintainPage/index.js index 4b9ee57..1d6a03a 100644 --- a/src/pages/pdf/PdfMaintainPage/index.js +++ b/src/pages/pdf/PdfMaintainPage/index.js @@ -1,8 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Button, Grid } from '@mui/material'; +import { Button, Grid, InputLabel, TextField } from '@mui/material'; import { GeneralConfirmWindow, notifySaveSuccess } from "../../../utils/CommonFunction"; import axios from 'axios'; -import {apiPath, appURL} from "../../../auth/utils"; +import {apiPath, adobeAPIKey} from "../../../auth/utils"; import { GET_PDF_TEMPLATE_PATH, GET_PDF_PATH, @@ -16,14 +16,19 @@ import {useLocation, useParams} from "react-router-dom"; // Import your chosen commercial PDF SDK (e.g., PSPDFKit) import PSPDFKit from 'pspdfkit'; +import WebViewer from '@compdfkit_pdf_sdk/webviewer'; +import Nutrient from "@nutrient-sdk/viewer"; import { CollectionsBookmarkRounded } from '../../../../node_modules/@mui/icons-material/index'; +import LoadingComponent from "../../extra-pages/LoadingComponent"; +import { fill } from 'lodash'; function PDF() { const viewerRef = useRef(null); // Ref for the DOM element where PDF will render const [pdfLoaded, setPdfLoaded] = useState(false); const [viewerLoaded, setViewerLoaded] = useState(false); const [viewInstance,setViewInstance] = useState(); - const [pdfBytes, setPdfBytes] = useState(); + const [adobeDCView,setAdobeDCView] = useState(); + const [pdfUrl, setPdfUrl] = useState(); const [record, setRecord] = useState(); const navigate = useNavigate() @@ -32,7 +37,7 @@ function PDF() { const queryParams = new URLSearchParams(location.search); const refId = queryParams.get("refId"); - const loadPdfForm = async (id, templateId = 0) => { + const loadPdfForm = async (id, templateId = 0, clientId = 0) => { if (!pdfLoaded) { if (id > 0) { // axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { @@ -60,7 +65,7 @@ function PDF() { byteNum[i] = byteChar.charCodeAt(i); } - setPdfBytes(byteNum); + handlePdfUrl(byteNum); setPdfLoaded(true); } }) @@ -71,11 +76,16 @@ function PDF() { } else { axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { + // axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}/${templateId}`, { responseType: 'arraybuffer', // Essential for binary data + params: { + templateId: templateId, + clientId: clientId, + }, }) .then((response) => { if (response.status === 200) { - setPdfBytes(response.data); + handlePdfUrl(response.data); setPdfLoaded(true); } }) @@ -87,66 +97,72 @@ function PDF() { } }; + const handlePdfUrl = (pdfBytes) => { + const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); + // const pdfFile = new File([pdfBlob], 'document.pdf', { type: 'application/pdf' }); + console.log(pdfBlob); + const pdfUrl = URL.createObjectURL(pdfBlob); + setPdfUrl(pdfUrl); + } + const loadPdfViewer = async () => { - if (pdfLoaded && viewerRef.current && !viewerLoaded) { + if (pdfLoaded && viewerRef.current && !viewerLoaded && !adobeDCView) { try { - //New try - const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); - console.log(pdfBlob); - const pdfUrl = URL.createObjectURL(pdfBlob); - - // console.log('Template: '); - // console.log(record); - // console.log('URL: '); - // console.log(pdfUrl); - // console.log('Viewer: '); - // console.log(viewerRef.current); - // await PSPDFKit.unload(viewerRef.current); - await PSPDFKit.load({//click into load - container: viewerRef.current, - // container: '#viewer', - document: pdfUrl, - // baseUrl: `./sdk/`, // Path to SDK assets - baseUrl: `${appURL}/sdk/`, // Path to SDK - // baseUrl: `${window.location.protocol}//${window.location.host}/${import.meta.env.BASE_URL}`, - // baseUrl: `https://cdn.jsdelivr.net/npm/pspdfkit@VERSION/dist/`, // Path to SDK assets so this is work? - // baseUrl: `${process.env.PUBLIC_URL}/`, // Path to SDK assets - disableWebAssemblyStreaming: true, - initialViewState: new PSPDFKit.ViewState({ - sidebarMode: PSPDFKit.SidebarMode.THUMBNAILS - }) - }) - .then(instance => { - URL.revokeObjectURL(pdfUrl); - setViewInstance(instance); - console.log('instance: '); - console.log(instance); - setViewerLoaded(true); - - // instance.addEventListener("formFields.load", (loadedFormFields) => { - // console.log("loaded fields:", loadedFormFields); - // }); - - const formFieldValues = instance.getFormFieldValues(); - // console.log(formFieldValues); // => { textField: 'Text Value', checkBoxField: ['A', 'B'], buttonField: null }e.log(getFormFieldValues); - }); - - // instanceRef.current = instance; - - // 3. Clean up on component unmount - // return () => { - // if (instance) { - // instance.unload(); // Unload SDK instance - // } - // URL.revokeObjectURL(pdfUrl); // Revoke the Blob URL - // }; - + loadAdobeSDK(); } catch (error) { console.error('Error loading PDF:', error); } } }; + const loadAdobeSDK = async() => { + // const token = localStorage.getItem('accessToken'); + if (window.AdobeDC) { + const DCViewer = (new window.AdobeDC.View({ + clientId: `${adobeAPIKey}`, + divId: 'adobe-dc-view', + })); + + setAdobeDCView(DCViewer); + + await DCViewer.previewFile( + { + content: { + location: { + url: pdfUrl, + // headers: [{ key: 'Authorization', value: `Bearer ${token}` }], + }, + }, + metaData: { fileName: 'document.pdf'/*templateName */ }, + }, + { + embedMode: 'FULL_WINDOW', + showAnnotationTools: true, + enableFormFilling: true, + } + + ).catch(error => { + console.error('Preview error:', error); + setError('Failed to load PDF: ' + error.message); + + }).then(instance => { + URL.revokeObjectURL(pdfUrl); + setViewInstance(instance); + console.log('Instance: ', instance); + setViewerLoaded(true); + }); + + DCViewer.registerCallback( + window.AdobeDC.View.Enum.CallbackType.SAVE_API, + handleSavePdf, + { autoSaveFrequency: 0, enableFormFilling: true } + ); + } else { + console.error('AdobeDC not available'); + setError('Adobe SDK not loaded'); + } + }; + useEffect(() => { if (params.id !== null) { const pdfData = (params.id).split("T"); @@ -154,53 +170,61 @@ function PDF() { if (pdfData[0] > 0) { // Existing Record loadPdfForm(pdfData[0]); } else { // New Record + const clientId = pdfData[0] * -1; + const templateId = pdfData[1] * 1; setRecord({ id: -1, - clientId: pdfData[0] * -1, //If PDF ID is negative, convert it to client ID - templateId: pdfData[1],}); - loadPdfForm(-1, pdfData[1]); // Load new Template + clientId: clientId, //If PDF ID is negative, convert it to client ID + templateId: templateId}); + loadPdfForm(-1, templateId, clientId); // Load new Template } } }, [params.id]); - // useEffect(() => { - // if (record) { - // console.log(record); - // loadPdfForm(); - // } - // }, [record]); + useEffect(() => { // Update Save function callback after record is updated + console.log("Record Updated: ",record); + if (record && adobeDCView) { + adobeDCView.registerCallback( + window.AdobeDC.View.Enum.CallbackType.SAVE_API, + handleSavePdf, + { autoSaveFrequency: 0, enableFormFilling: true } + ); + } + }, [record]); useEffect(() => { loadPdfViewer(); }, [viewerRef.current, pdfLoaded]); - const handleSavePdf = async () => { - if (viewInstance) { - try { - // Export the filled PDF from the SDK as an ArrayBuffer - const arrayBuffer = await viewInstance.exportPDF(); - const filledPdfBlob = new Blob([arrayBuffer], { type: 'application/pdf' }); - // Create FormData to send the file to Spring Boot - const formData = new FormData(); - formData.append('file', filledPdfBlob, 'filled_form.pdf'); - formData.append('record', JSON.stringify(record)); - // Send the filled PDF to your Spring Boot backend's save endpoint - const response = await axios.post(`${apiPath}${POST_PDF_PATH}`, formData, { - headers: { - 'Content-Type': 'multipart/form-data' // Important for file uploads - }, - }); + const handleSavePdf = async (metaData, content, options) => { + try { + const filledPdfBlob = new Blob([content], { type: 'application/pdf' }); + // Create FormData to send the file to Spring Boot + const formData = new FormData(); + formData.append('file', filledPdfBlob, 'filled_form.pdf'); + formData.append('record', JSON.stringify(record)); + // Send the filled PDF to your Spring Boot backend's save endpoint + await axios.post(`${apiPath}${POST_PDF_PATH}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' // Important for file uploads + }, + }) + .then(response => { console.log('PDF saved on server:', response.data); - notifySaveSuccess(); - - if (viewerLoaded) { - await PSPDFKit.unload(viewerRef.current); - } - navigate(`/pdf/${record.clientId}`); - } catch (error) { - console.error('Error saving PDF:', error); - alert('Failed to save PDF.'); - } + setRecord({ + id: response.data.data.id, + clientId: record.clientId, + templateId: record.templateId + }); + notifySaveSuccess()}); + return { + code: window.AdobeDC.View.Enum.ApiResponseCode.SUCCESS, + data: { metaData } // Return metaData to prevent t.data undefined + }; + } catch (error) { + console.error('Error saving PDF:', error); + alert('Failed to save PDF.'); + return { code: window.AdobeDC.View.Enum.ApiResponseCode.FAIL }; } }; @@ -224,11 +248,22 @@ function PDF() { const handleBack = async () => { if (viewerLoaded) { - await PSPDFKit.unload(viewerRef.current); + await Nutrient.unload(viewerRef.current); } navigate(`/pdf/${record.clientId}`); }; + const handleTest = () => { + // const element = document.getElementById('pdfViewer'); + // const inputFields = element.querySelectorAll('iframe')[0]; + // console.log(element); + // console.log(inputFields.contentDocument); + + // inputFields.forEach(input => { + // console.log('Name:', input.name, 'Value:', input.value); + // }); + }; + return (
{/* This is your 'pdfForm' page */} @@ -236,15 +271,16 @@ function PDF() { - + */} + + {/* + Remarks + + + + */} {/* {pdfLoaded ? ( @@ -278,18 +326,25 @@ function PDF() {

Loading PDF viewer...

)} */} -
)} + */}
); diff --git a/src/pages/pdf/PdfSearchPage/PdfSearchForm.js b/src/pages/pdf/PdfSearchPage/PdfSearchForm.js index 5bd5839..6d6194a 100644 --- a/src/pages/pdf/PdfSearchPage/PdfSearchForm.js +++ b/src/pages/pdf/PdfSearchPage/PdfSearchForm.js @@ -41,6 +41,12 @@ import {ExpandMore} from "@mui/icons-material"; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { isNull } from 'lodash'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogActions from '@mui/material/DialogActions'; +import Dialog from '@mui/material/Dialog'; + // ==============================|| DASHBOARD - DEFAULT ||============================== // // const EventSearchForm = ({applySearch, refTemplateData, isUpdating, @@ -50,6 +56,8 @@ const PdfSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { const navigate = useNavigate() const ability = useContext(AbilityContext); + const [isWindowOpen, setIsWindowOpen] = useState(false); + const [createDateFrom, setCreateDateFrom] = useState(null); const [createDateTo, setCreateDateTo] = useState(null); @@ -77,25 +85,25 @@ const PdfSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { } }, [createDateToError]); - const createNewForm = () => { - navigate(`/pdf/maintain/-${clientId}T${1}`); + const createNewForm = (templateId) => { + navigate(`/pdf/maintain/-${clientId}T${templateId}`); }; - const createFormUpDown = () => { - navigate(`/pdf/form-up-down/-${clientId}T${1}`); - }; + // const createFormUpDown = () => { + // navigate(`/pdf/form-up-down/-${clientId}T${1}`); + // }; - const createFormIDA = () => { - navigate(`/pdf/newIDA/${clientId}`); - }; + // const createFormIDA = () => { + // navigate(`/pdf/newIDA/${clientId}`); + // }; - const createFormFNA = () => { - navigate(`/pdf/newFNA/${clientId}`); - }; + // const createFormFNA = () => { + // navigate(`/pdf/newFNA/${clientId}`); + // }; - const createFormHSBCFIN = () => { - navigate(`/pdf/newHSBCFIN/${clientId}`); - }; + // const createFormHSBCFIN = () => { + // navigate(`/pdf/newHSBCFIN/${clientId}`); + // }; const onSubmit = (data) => { const criteria = { @@ -299,26 +307,90 @@ const PdfSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { - - {/* - + + + {ability.can('EDIT','EVENT') ? + + + + setIsWindowOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + PaperProps={{ + sx: { + maxWidth: { xs: '90vw', s: '90vw', m: '70vw', lg: '30vw' }, + maxHeight: { xs: '90vh', s: '70vh', m: '70vh', lg: '50vh' } + } + }} + > + + + Select Template + + + + + + + + + + + + + + + + + + + {/* */} + + : + + } - - - */} - + + {/* - + */} diff --git a/src/pages/pdf/PdfSearchPage/PdfTable.js b/src/pages/pdf/PdfSearchPage/PdfTable.js index 389517d..63087ab 100644 --- a/src/pages/pdf/PdfSearchPage/PdfTable.js +++ b/src/pages/pdf/PdfSearchPage/PdfTable.js @@ -85,9 +85,22 @@ export default function PdfTable({recordList}) { headerName: 'Form Name', flex: 2, renderCell: (params) => { + const getFormLabel = (value) => { + switch (value) { + case 1: + return 'Lioner IDA'; + case 2: + return 'Lioner FNA'; + case 3: + return 'HSBC FIN'; + default: + return 'Form'; + } + }; + return (
- {params.value} + {getFormLabel(params.value)}
); } @@ -203,9 +216,6 @@ export default function PdfTable({recordList}) { return (
-

- Existing Forms -

{ const [onReady, setOnReady] = useState(false); const [expanded, setExpanded] = React.useState(true); + const [clientInfo,setClientInfo] = useState([]); const [record,setRecord] = useState([]); const [searchCriteria, setSearchCriteria] = useState({}); const params = useParams(); @@ -62,6 +64,21 @@ const PdfSearchPage = () => { } useEffect(() => { + if (params.id) { + if (params.id > 0) {console.log("loggg"); + axios.get(`${apiPath}${GET_CLIENT_PATH}/${params.id}` + ) + .then((response) => { + if (response.status === 200) { + setClientInfo(response.data); + } + }) + .catch(error => { + console.log(error); + return false; + }); + } + } setSearchCriteria({clientId: params.id, ...searchCriteria}); }, [params.id]); @@ -80,6 +97,15 @@ const PdfSearchPage = () => { return ( + + + + {clientInfo.data? clientInfo.data.fullname:""} + + + + { /> + + + + Existing Forms + + + + {!onReady? : // PDF Table
diff --git a/src/routes/ClientRoutes.js b/src/routes/ClientRoutes.js index 2836404..7682af8 100644 --- a/src/routes/ClientRoutes.js +++ b/src/routes/ClientRoutes.js @@ -15,6 +15,7 @@ const PdfMaintainPage = Loadable(lazy(() => import('pages/pdf/PdfMaintainPage')) 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'))); // ==============================|| AUTH ROUTING ||============================== // @@ -64,7 +65,17 @@ const ClientRoutes =() => { ) ), - }, + }, + { + path: '/template/', + element: ( + handleRouteAbility( + ability.can('VIEW', 'DASHBOARD'), + , + + ) + ), + }, { path: '/pdf/form-up-down/:id', element: ( diff --git a/src/routes/MainRoutes.js b/src/routes/MainRoutes.js index 0a29371..1cd5bda 100644 --- a/src/routes/MainRoutes.js +++ b/src/routes/MainRoutes.js @@ -6,6 +6,7 @@ import MainLayout from 'layout/MainLayout'; import AbilityContext from "../components/AbilityProvider"; import {handleRouteAbility} from "../utils/CommonFunction"; import {Navigate} from "react-router"; +import ClientSearchPage from 'pages/client/ClientSearchPage/index'; // render - dashboard //const DashboardDefault = Loadable(lazy(() => import('pages/dashboard'))); @@ -47,7 +48,8 @@ const MainRoutes = () => { element: ( handleRouteAbility( ability.can('VIEW', 'DASHBOARD'), - , + , + // , ) ), diff --git a/src/utils/CommonFunction.js b/src/utils/CommonFunction.js index e2e2530..adde6b7 100644 --- a/src/utils/CommonFunction.js +++ b/src/utils/CommonFunction.js @@ -878,6 +878,20 @@ export function UploadFileWindow({ isWindowOpen, title, video, onNormalClose, on }); } } + + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + const droppedFiles = Array.from(e.dataTransfer.files); + console.log(droppedFiles); + // setFiles((prevFiles) => [...prevFiles, ...droppedFiles]); + }; + return ( ) : ( - + + + + + + + + + +
+ + + {/*Selected file: {file ? file.name : 'None'}*/} +
+
+
+
+
+
)}