| @@ -7,10 +7,10 @@ | |||||
| ``` | ``` | ||||
| ## 2. Config the library | ## 2. Config the library | ||||
| - Navigate to "LIONER-frontend\node_modules\pspdfkit\dist" | |||||
| - Copy the folder "pspdfkit-lib" | |||||
| - Create a new folder "sdk" in "LIONER-frontend\public" | |||||
| - Paste the "pspdfkit-lib" inside "sdk" folder | |||||
| - Navigate to **"LIONER-frontend\node_modules\pspdfkit\dist"** | |||||
| - Copy the folder **"pspdfkit-lib"** | |||||
| - Create a new folder **"sdk"** in **"LIONER-frontend\public"** | |||||
| - Paste the **"pspdfkit-lib"** inside **"sdk"** folder | |||||
| ## 3. Run the application | ## 3. Run the application | ||||
| - Start the application by the following command: | - Start the application by the following command: | ||||
| @@ -35,8 +35,8 @@ export default function ClientTable({recordList}) { | |||||
| navigate('/client/maintain/'+ id); | navigate('/client/maintain/'+ id); | ||||
| }; | }; | ||||
| const handlePDFClick = () => () => { | |||||
| navigate('/pdf'); | |||||
| const handlePdfClick = (id) => () => { | |||||
| navigate(`/pdf/${id}`); | |||||
| }; | }; | ||||
| const columns = [ | const columns = [ | ||||
| @@ -63,7 +63,7 @@ export default function ClientTable({recordList}) { | |||||
| icon={<PictureAsPdfIcon sx={{fontSize: 25}}/>} | icon={<PictureAsPdfIcon sx={{fontSize: 25}}/>} | ||||
| label="Edit PDF" | label="Edit PDF" | ||||
| className="textPrimary" | className="textPrimary" | ||||
| onClick={handlePDFClick()} | |||||
| onClick={handlePdfClick(id)} | |||||
| color="edit" | color="edit" | ||||
| // disabled={'true'} | // disabled={'true'} | ||||
| // disabled={!ability.can('VIEW','DASHBOARD')} | // disabled={!ability.can('VIEW','DASHBOARD')} | ||||
| @@ -1,61 +1,107 @@ | |||||
| import React, { useEffect, useRef, useState } from 'react'; | import React, { useEffect, useRef, useState } from 'react'; | ||||
| import { Button, Grid } from '@mui/material'; | import { Button, Grid } from '@mui/material'; | ||||
| import { GeneralConfirmWindow } from "../../utils/CommonFunction"; | |||||
| import { GeneralConfirmWindow, notifySaveSuccess } from "../../../utils/CommonFunction"; | |||||
| import axios from 'axios'; | import axios from 'axios'; | ||||
| import {apiPath} from "../../auth/utils"; | |||||
| import {apiPath} from "../../../auth/utils"; | |||||
| import { | import { | ||||
| GET_PDF_TEMPLATE_PATH, | |||||
| GET_PDF_PATH, | GET_PDF_PATH, | ||||
| POST_PDF_PATH | POST_PDF_PATH | ||||
| } from "../../utils/ApiPathConst"; | |||||
| import {LIONER_BUTTON_THEME} from "../../themes/colorConst"; | |||||
| } from "../../../utils/ApiPathConst"; | |||||
| import {LIONER_BUTTON_THEME} from "../../../themes/colorConst"; | |||||
| import {ThemeProvider} from "@emotion/react"; | import {ThemeProvider} from "@emotion/react"; | ||||
| import {useNavigate} from "react-router-dom"; | import {useNavigate} from "react-router-dom"; | ||||
| import {useForm} from "react-hook-form"; | |||||
| import {useLocation, useParams} from "react-router-dom"; | |||||
| // Import your chosen commercial PDF SDK (e.g., PSPDFKit) | // Import your chosen commercial PDF SDK (e.g., PSPDFKit) | ||||
| import PSPDFKit from 'pspdfkit'; | import PSPDFKit from 'pspdfkit'; | ||||
| import { CollectionsBookmarkRounded } from '../../../../node_modules/@mui/icons-material/index'; | |||||
| function PDF() { | function PDF() { | ||||
| const viewerRef = useRef(null); // Ref for the DOM element where PDF will render | const viewerRef = useRef(null); // Ref for the DOM element where PDF will render | ||||
| const instanceRef = useRef(null); // Ref for the PSPDFKit instance | |||||
| const [pdfLoaded, setPdfLoaded] = useState(false); | const [pdfLoaded, setPdfLoaded] = useState(false); | ||||
| const [viewerLoaded, setViewerLoaded] = useState(false); | const [viewerLoaded, setViewerLoaded] = useState(false); | ||||
| const [template,setTemplate] = useState(); | |||||
| const [viewInstance,setViewInstance] = useState(); | const [viewInstance,setViewInstance] = useState(); | ||||
| const [pdfBytes, setPdfBytes] = useState(); | |||||
| const [record, setRecord] = useState(); | |||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const params = useParams(); | |||||
| const location = useLocation(); | |||||
| const queryParams = new URLSearchParams(location.search); | |||||
| const refId = queryParams.get("refId"); | |||||
| const loadPdfTemplate = async () => { | |||||
| // if (!pdfLoaded) { | |||||
| axios.get(`${apiPath}${GET_PDF_PATH}`, { | |||||
| responseType: 'arraybuffer', // Essential for binary data | |||||
| }) | |||||
| .then((response) => { | |||||
| if (response.status === 200) { | |||||
| setTemplate(response.data); | |||||
| setPdfLoaded(true); | |||||
| } | |||||
| }) | |||||
| .catch(error => { | |||||
| console.log(error); | |||||
| return false; | |||||
| }); | |||||
| // } | |||||
| const loadPdfForm = async (id, templateId = 0) => { | |||||
| if (!pdfLoaded) { | |||||
| if (id > 0) { | |||||
| // axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { | |||||
| axios.get(`${apiPath}${GET_PDF_PATH}/${id}`, { | |||||
| // responseType: 'arraybuffer', // Essential for binary data | |||||
| }) | |||||
| .then((response) => { | |||||
| if (response.status === 200) { | |||||
| // setRecord(rec => { | |||||
| // const { ["blobValue"]: _, ...newData } = response.data; // Destructure to remove blobValue | |||||
| // return newData; | |||||
| // }); | |||||
| const res = response.data; | |||||
| setRecord({ // WIP - allow to update all record | |||||
| id : res.id, | |||||
| clientId: res.clientId, | |||||
| templateId: res.templateId | |||||
| }) | |||||
| //Convert Base64 to binary data | |||||
| const byteChar = atob(response.data.blobValue); | |||||
| const byteNum = new Uint8Array(byteChar.length); | |||||
| for (let i = 0; i < byteChar.length; i++) { | |||||
| byteNum[i] = byteChar.charCodeAt(i); | |||||
| } | |||||
| setPdfBytes(byteNum); | |||||
| setPdfLoaded(true); | |||||
| } | |||||
| }) | |||||
| .catch(error => { | |||||
| console.log(error); | |||||
| return false; | |||||
| }); | |||||
| } else { | |||||
| axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { | |||||
| responseType: 'arraybuffer', // Essential for binary data | |||||
| }) | |||||
| .then((response) => { | |||||
| if (response.status === 200) { | |||||
| setPdfBytes(response.data); | |||||
| setPdfLoaded(true); | |||||
| } | |||||
| }) | |||||
| .catch(error => { | |||||
| console.log(error); | |||||
| return false; | |||||
| }); | |||||
| } | |||||
| } | |||||
| }; | }; | ||||
| const loadPdfViewer = async () => {console.log(`PDF loaded = ${pdfLoaded}`); | |||||
| if (pdfLoaded && viewerRef.current) { | |||||
| const loadPdfViewer = async () => { | |||||
| if (pdfLoaded && viewerRef.current && !viewerLoaded) { | |||||
| try { | try { | ||||
| //New try | //New try | ||||
| console.log(typeof(template)); | |||||
| const pdfBlob = new Blob([template], { type: 'application/pdf' }); | |||||
| const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); | |||||
| console.log(pdfBlob); | |||||
| const pdfUrl = URL.createObjectURL(pdfBlob); | const pdfUrl = URL.createObjectURL(pdfBlob); | ||||
| console.log('Template: '); | |||||
| console.log(template.data); | |||||
| console.log('URL: '); | |||||
| console.log(pdfUrl); | |||||
| console.log('Viewer: '); | |||||
| console.log(viewerRef.current); | |||||
| PSPDFKit.unload(viewerRef.current); | |||||
| // 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 | await PSPDFKit.load({//click into load | ||||
| container: viewerRef.current, | container: viewerRef.current, | ||||
| // container: '#viewer', | // container: '#viewer', | ||||
| @@ -76,6 +122,13 @@ function PDF() { | |||||
| console.log('instance: '); | console.log('instance: '); | ||||
| console.log(instance); | console.log(instance); | ||||
| setViewerLoaded(true); | 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; | // instanceRef.current = instance; | ||||
| @@ -95,32 +148,55 @@ function PDF() { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| loadPdfTemplate(); | |||||
| }, []); | |||||
| if (params.id !== null) { | |||||
| const pdfData = (params.id).split("T"); | |||||
| if (pdfData[0] > 0) { // Existing Record | |||||
| loadPdfForm(pdfData[0]); | |||||
| } else { // New Record | |||||
| 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 | |||||
| } | |||||
| } | |||||
| }, [params.id]); | |||||
| // useEffect(() => { | |||||
| // if (record) { | |||||
| // console.log(record); | |||||
| // loadPdfForm(); | |||||
| // } | |||||
| // }, [record]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadPdfViewer(); | loadPdfViewer(); | ||||
| }, [viewerRef, pdfLoaded]); | |||||
| }, [viewerRef.current, pdfLoaded]); | |||||
| const handleSavePdf = async () => { | const handleSavePdf = async () => { | ||||
| if (viewInstance.current) {console.log("tetes"); | |||||
| if (viewInstance) { | |||||
| try { | try { | ||||
| // Export the filled PDF from the SDK as an ArrayBuffer | // Export the filled PDF from the SDK as an ArrayBuffer | ||||
| const arrayBuffer = await viewInstance.current.exportPDF(); | |||||
| const arrayBuffer = await viewInstance.exportPDF(); | |||||
| const filledPdfBlob = new Blob([arrayBuffer], { type: 'application/pdf' }); | const filledPdfBlob = new Blob([arrayBuffer], { type: 'application/pdf' }); | ||||
| // Create FormData to send the file to Spring Boot | // Create FormData to send the file to Spring Boot | ||||
| const formData = new FormData(); | const formData = new FormData(); | ||||
| formData.append('file', filledPdfBlob, 'filled_form.pdf'); | 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 | // Send the filled PDF to your Spring Boot backend's save endpoint | ||||
| const response = await axios.post(`${apiPath}${POST_PDF_PATH}`, formData, { | const response = await axios.post(`${apiPath}${POST_PDF_PATH}`, formData, { | ||||
| headers: { | headers: { | ||||
| 'Content-Type': 'multipart/form-data' // Important for file uploads | 'Content-Type': 'multipart/form-data' // Important for file uploads | ||||
| } | |||||
| }, | |||||
| }); | }); | ||||
| console.log('PDF saved on server:', response.data); | console.log('PDF saved on server:', response.data); | ||||
| alert('PDF saved successfully on server!'); | |||||
| notifySaveSuccess(); | |||||
| if (viewerLoaded) { | |||||
| await PSPDFKit.unload(viewerRef.current); | |||||
| } | |||||
| navigate(`/pdf/${record.clientId}`); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error saving PDF:', error); | console.error('Error saving PDF:', error); | ||||
| alert('Failed to save PDF.'); | alert('Failed to save PDF.'); | ||||
| @@ -146,8 +222,11 @@ function PDF() { | |||||
| setIsWindowOpen(true); | setIsWindowOpen(true); | ||||
| }; | }; | ||||
| const handleBack = () => { | |||||
| navigate('/client'); | |||||
| const handleBack = async () => { | |||||
| if (viewerLoaded) { | |||||
| await PSPDFKit.unload(viewerRef.current); | |||||
| } | |||||
| navigate(`/pdf/${record.clientId}`); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -162,7 +241,7 @@ function PDF() { | |||||
| type="submit" | type="submit" | ||||
| color="save" | color="save" | ||||
| disabled={!viewerLoaded} | disabled={!viewerLoaded} | ||||
| // onClick={handleSavePdf} | |||||
| onClick={handleSavePdf} | |||||
| > | > | ||||
| Save | Save | ||||
| </Button> | </Button> | ||||
| @@ -0,0 +1,312 @@ | |||||
| // 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 {useContext, useEffect, useState} from "react"; | |||||
| import Autocomplete from '@mui/material/Autocomplete'; | |||||
| import * as React from "react"; | |||||
| // import axios from "axios"; | |||||
| // import {apiPath, getUserData} from "../../../auth/utils"; | |||||
| // import { | |||||
| // GET_CONSULTANT_COMBO_LIST, GET_EVENT_EXPORT, | |||||
| // GET_SUB_CONSULTANT_COMBO_LIST, POST_SEARCH_TEMPLATE_PATH, VALIDATE_TEMPLATE_NAME_PATH, | |||||
| // } from "../../../utils/ApiPathConst"; | |||||
| import {LIONER_BUTTON_THEME} from "../../../themes/colorConst"; | |||||
| // import { | |||||
| // GeneralCreateTemplateWindow, | |||||
| // getComboValueByIdList, getComboValueByLabel, | |||||
| // getIdList, isOptionEqualToValue, notifySaveSuccess, | |||||
| // } from "../../../utils/CommonFunction"; | |||||
| // import Qs from 'qs'; | |||||
| import {useNavigate} from "react-router"; | |||||
| import {ThemeProvider} from "@emotion/react"; | |||||
| // import UploadContext from "../../../components/UploadProvider"; | |||||
| // import {EVENT_REGION_COMBO, EVENT_TYPE_COMBO} from "../../../utils/ComboConst"; | |||||
| import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; | |||||
| import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import {DemoItem} from "@mui/x-date-pickers/internals/demo"; | |||||
| import {DatePicker} from "@mui/x-date-pickers/DatePicker"; | |||||
| import dayjs from "dayjs"; | |||||
| // import {isObjEmpty} from "../../../utils/Utils"; | |||||
| // import {useLocation} from "react-router-dom"; | |||||
| 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'; | |||||
| import { isNull } from 'lodash'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | |||||
| // const EventSearchForm = ({applySearch, refTemplateData, isUpdating, | |||||
| // setIsUpdating, getTemplateList,setExpanded,expanded,userConsultant}) => { | |||||
| const PdfSearchForm = ({applySearch, setExpanded,expanded, clientId}) => { | |||||
| const navigate = useNavigate() | |||||
| const ability = useContext(AbilityContext); | |||||
| 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 createNewForm = () => { | |||||
| navigate(`/pdf/maintain/-${clientId}T${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 onExport(){ | |||||
| // const data = getValues(); | |||||
| // const temp = { | |||||
| // ...data, | |||||
| // fromDate: fromDate === null ? null : dayjs(fromDate).format('YYYY-MM-DD'), | |||||
| // createDateTo: createDateTo === null ? null : dayjs(createDateTo).format('YYYY-MM-DD'), | |||||
| // region: selectedRegion === null ? null : selectedRegion.label, | |||||
| // type: selectedType === null ? null :selectedType.label, | |||||
| // consultantIdList: getIdList(selectedConsultants), | |||||
| // caseManagerIdList: getIdList(selectedCaseManager), | |||||
| // }; | |||||
| // setIsUploading(true); | |||||
| // axios.get(`${apiPath}${GET_EVENT_EXPORT}`,{ | |||||
| // responseType: 'blob', | |||||
| // params: temp, | |||||
| // paramsSerializer: function (params) { | |||||
| // return Qs.stringify(params, { arrayFormat: 'repeat' }); | |||||
| // }, | |||||
| // }) | |||||
| // .then((response) => { | |||||
| // if (response.status === 200) { | |||||
| // setIsUploading(false); | |||||
| // const url = URL.createObjectURL(new Blob([response.data])); | |||||
| // const link = document.createElement("a"); | |||||
| // link.href = url; | |||||
| // link.setAttribute("download", "event_export_"+ Date.now() + ".xlsx"); | |||||
| // link.click(); | |||||
| // } | |||||
| // }) | |||||
| // .catch(error => { | |||||
| // setIsUploading(false); | |||||
| // console.log(error); | |||||
| // return false; | |||||
| // }); | |||||
| // } | |||||
| function resetForm(){ | |||||
| setCreateDateFrom(null); | |||||
| setCreateDateTo(null); | |||||
| reset(); | |||||
| } | |||||
| const handleBack = () => { | |||||
| navigate('/client'); | |||||
| }; | |||||
| return ( | |||||
| <MainCard xs={12} md={12} lg={12} | |||||
| content={false} | |||||
| sx={{width:CARD_MAX_WIDTH}} | |||||
| > | |||||
| <Grid item onClick={handleExpandClick}> | |||||
| <CardActions disableSpacing> | |||||
| {/*row 1*/} | |||||
| <Grid item justifyContent="space-between" alignItems="center" sx={{mt:1,ml:2,mb:1}}> | |||||
| <Typography variant="lionerSubLabel" > | |||||
| Search Criteria(s) | |||||
| </Typography> | |||||
| </Grid> | |||||
| <ExpandMore | |||||
| expand={expanded.toString()} | |||||
| onClick={handleExpandClick} | |||||
| aria-expanded={expanded} | |||||
| aria-label="show more" | |||||
| style={{ marginLeft: 'auto' }} // Align the button to the right | |||||
| > | |||||
| <ExpandMoreIcon /> | |||||
| </ExpandMore> | |||||
| </CardActions> | |||||
| </Grid> | |||||
| <Collapse in={expanded} timeout="auto" unmountOnExit> | |||||
| <form onSubmit={handleSubmit(onSubmit)}> | |||||
| <ThemeProvider theme={LIONER_FORM_THEME}> | |||||
| {/*row 2*/} | |||||
| <Grid container alignItems={"flex-start"} > | |||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, mb:0.5}}> | |||||
| <InputLabel htmlFor="template">Form</InputLabel> | |||||
| <TextField | |||||
| fullWidth | |||||
| {...register("templateId")} | |||||
| id='templateId' | |||||
| inputProps={{maxLength: 255}} | |||||
| // label="Client Name" | |||||
| autoComplete="off" | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, mb:0.5}}> | |||||
| <InputLabel htmlFor="createDateFrom">Form Create Date</InputLabel> | |||||
| <Grid container> | |||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DemoItem> | |||||
| <DatePicker | |||||
| id="createDateFrom" | |||||
| onError={(newError) => setCreateDateFromError(newError)} | |||||
| slotProps={{ | |||||
| field: { clearable: true }, | |||||
| textField: { | |||||
| helperText: createDateFromErrorMessage, | |||||
| }, | |||||
| }} | |||||
| format="DD/MM/YYYY" | |||||
| value={createDateFrom === null ? null : dayjs(createDateFrom)} | |||||
| onChange={(newValue) => setCreateDateFrom(newValue)} | |||||
| // label="From" | |||||
| /> | |||||
| </DemoItem > | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| <Grid item xs={1.5} s={1.5} md={1.5} lg={1} sx={{mt:1.3, display: 'flex', justifyContent:"center", alignItems: 'flex-start'}}> | |||||
| To | |||||
| </Grid> | |||||
| <Grid item xs={5.25} s={5.25} md={5.25} lg={5.5}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DemoItem components={['DatePicker']}> | |||||
| <DatePicker | |||||
| format="DD/MM/YYYY" | |||||
| onError={(newError) => setCreateDateToError(newError)} | |||||
| slotProps={{ | |||||
| field: { clearable: true }, | |||||
| textField: { | |||||
| helperText: createDateToErrorMessage, | |||||
| }, | |||||
| }} | |||||
| id="createDateTo" | |||||
| //label="To Date" | |||||
| value={createDateTo === null ? null : dayjs(createDateTo)} | |||||
| onChange={(newValue) => setCreateDateTo(newValue)} | |||||
| /> | |||||
| </DemoItem > | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid item xs={9} s={6} md={5} lg={3} sx={{ml:3, mr:3, mb:0.5}}> | |||||
| <InputLabel htmlFor="remarks">Remarks</InputLabel> | |||||
| <TextField | |||||
| fullWidth | |||||
| {...register("remarks")} | |||||
| inputProps={{maxLength: 500}} | |||||
| id="remarks" | |||||
| autoComplete="off" | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| {/*last row*/} | |||||
| <Grid container maxWidth justifyContent="space-between" sx={{mt:1.5}}> | |||||
| <ThemeProvider theme={LIONER_BUTTON_THEME}> | |||||
| <Grid item> | |||||
| <Grid container> | |||||
| <Grid item sx={{ml:3, mr:1.5, mb:2}}> | |||||
| <Button | |||||
| variant="contained" | |||||
| type="submit" | |||||
| color="save" | |||||
| disabled={createDateFromError || createDateToError} | |||||
| onClick={applySearch} | |||||
| > | |||||
| Search | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item sx={{ml:{xs:1.5, md:1.5, lg:1.5}, mr:1.5, mb:2}}> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="cancel" | |||||
| onClick={resetForm} | |||||
| > | |||||
| Reset | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item sx={{ml:{xs:1.5, md:1.5, lg:1.5}, mr:1.5, mb:2}}> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="cancel" | |||||
| onClick={handleBack} | |||||
| > | |||||
| Back | |||||
| </Button> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid item> | |||||
| <Grid container> | |||||
| {ability.can('EDIT','EVENT') ? | |||||
| <Grid item sx={{ml:3, mr:3, mb:0.5}}> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="create" | |||||
| onClick={createNewForm} | |||||
| > | |||||
| New Form | |||||
| </Button> | |||||
| </Grid> | |||||
| : | |||||
| <Grid/> | |||||
| } | |||||
| </Grid> | |||||
| </Grid> | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| </ThemeProvider> | |||||
| </form> | |||||
| </Collapse> | |||||
| </MainCard> | |||||
| ); | |||||
| }; | |||||
| export default PdfSearchForm; | |||||
| @@ -0,0 +1,220 @@ | |||||
| // material-ui | |||||
| import * as React from 'react'; | |||||
| import { | |||||
| DataGrid, | |||||
| GridActionsCellItem, | |||||
| } from "@mui/x-data-grid"; | |||||
| import EditIcon from '@mui/icons-material/Edit'; | |||||
| import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; | |||||
| 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"; | |||||
| // ==============================|| PDF TABLE ||============================== // | |||||
| export default function PdfTable({recordList}) { | |||||
| const [rows, setRows] = React.useState(recordList); | |||||
| const [rowModesModel] = React.useState({}); | |||||
| 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(`/pdf/maintain/${id}`); | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| field: 'actions', | |||||
| type: 'actions', | |||||
| headerName: 'Actions', | |||||
| // flex: 0.5, | |||||
| width: 100, | |||||
| cellClassName: 'actions', | |||||
| getActions: ({id}) => { | |||||
| return [ | |||||
| <ThemeProvider key="OutSave" theme={LIONER_BUTTON_THEME}> | |||||
| <GridActionsCellItem | |||||
| icon={<EditIcon sx={{fontSize: 25}}/>} | |||||
| label="Edit" | |||||
| className="textPrimary" | |||||
| onClick={handleEditClick(id)} | |||||
| color="edit" | |||||
| // disabled={'true'} | |||||
| // disabled={!ability.can('VIEW','DASHBOARD')} | |||||
| /> | |||||
| </ThemeProvider> | |||||
| ] | |||||
| }, | |||||
| }, | |||||
| // { | |||||
| // id: 'title', | |||||
| // field: 'title', | |||||
| // headerName: 'Title', | |||||
| // // sortComparator: dateComparator, | |||||
| // flex: 0.75, | |||||
| // renderCell: (params) => ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // // <div> | |||||
| // // {getDateString(params.row.pdfFrom,false)} | |||||
| // // </div> | |||||
| // ), | |||||
| // }, | |||||
| { | |||||
| id: 'templateId', | |||||
| field: 'templateId', | |||||
| headerName: 'Form Name', | |||||
| flex: 2, | |||||
| renderCell: (params) => { | |||||
| return ( | |||||
| <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| {params.value} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| }, | |||||
| { | |||||
| id: 'createDate', | |||||
| field: 'createDate', | |||||
| headerName: 'Create Date', | |||||
| flex: 1, | |||||
| sortComparator: dateComparator, | |||||
| renderCell: (params) => ( | |||||
| <div> | |||||
| {getDateString(params.row.created, 'dd/MM/yyyy')} | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| id: 'modified', | |||||
| field: 'modified', | |||||
| headerName: 'Modified Date', | |||||
| flex: 1, | |||||
| sortComparator: dateComparator, | |||||
| renderCell: (params) => ( | |||||
| <div> | |||||
| {getDateString(params.row.modified, 'dd/MM/yyyy')} | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| // { | |||||
| // id: 'lastname', | |||||
| // field: 'lastname', | |||||
| // headerName: 'Last Name', | |||||
| // flex: 1.5, | |||||
| // sortComparator: dateComparator, | |||||
| // renderCell: (params) => ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // // <div> | |||||
| // // {getDateString(params.row.startDate,false)} | |||||
| // // </div> | |||||
| // ), | |||||
| // }, | |||||
| // { | |||||
| // id: 'firstname', | |||||
| // field: 'firstname', | |||||
| // headerName: 'First Name', | |||||
| // // sortComparator: dateComparator, | |||||
| // flex: 2, | |||||
| // renderCell: (params) => ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // // <div> | |||||
| // // {getDateString(params.row.applicationDeadline,false)} | |||||
| // // </div> | |||||
| // ), | |||||
| // }, | |||||
| // { | |||||
| // id: 'email', | |||||
| // field: 'email', | |||||
| // headerName: 'Email', | |||||
| // flex: 1.5, | |||||
| // renderCell: (params) => { | |||||
| // return ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // ); | |||||
| // } | |||||
| // }, | |||||
| // { | |||||
| // id: 'phone1', | |||||
| // field: 'phone1', | |||||
| // headerName: 'Phone No.', | |||||
| // flex: 1, | |||||
| // renderCell: (params) => { | |||||
| // return ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // ); | |||||
| // } | |||||
| // }, | |||||
| // { | |||||
| // id: 'phone2', | |||||
| // field: 'phone2', | |||||
| // headerName: '2nd Phone No.', | |||||
| // flex: 1, | |||||
| // renderCell: (params) => { | |||||
| // return ( | |||||
| // <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| // {params.value} | |||||
| // </div> | |||||
| // ); | |||||
| // } | |||||
| // }, | |||||
| { | |||||
| id: 'remarks', | |||||
| field: 'remarks', | |||||
| headerName: 'Remarks', | |||||
| flex: 2, | |||||
| renderCell: (params) => { | |||||
| return ( | |||||
| <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||||
| {params.value} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <DataGrid | |||||
| rows={rows} | |||||
| columns={columns} | |||||
| columnHeaderHeight={45} | |||||
| editMode="row" | |||||
| //autoPageSize | |||||
| rowModesModel={rowModesModel} | |||||
| getRowHeight={() => 'auto'} | |||||
| paginationModel={paginationModel} | |||||
| onPaginationModelChange={setPaginationModel} | |||||
| slots={{ | |||||
| noRowsOverlay: () => ( | |||||
| CustomNoRowsOverlay() | |||||
| ) | |||||
| }} | |||||
| pageSizeOptions={[10]} | |||||
| autoHeight | |||||
| /> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,132 @@ | |||||
| // 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_DIVISION_FROM_SUB_DIVISION, | |||||
| GET_PDF_PATH, | |||||
| // GET_SEARCH_TEMPLATE_COMBO_PATH, | |||||
| // GET_SEARCH_TEMPLATE_PATH | |||||
| } from "../../../utils/ApiPathConst"; | |||||
| import * as React from "react"; | |||||
| import LoadingComponent from "../../extra-pages/LoadingComponent"; | |||||
| import PdfTable from "./PdfTable"; | |||||
| import PdfSearchForm from "./PdfSearchForm"; | |||||
| 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 PdfSearchPage = () => { | |||||
| const [onReady, setOnReady] = useState(false); | |||||
| const [expanded, setExpanded] = React.useState(true); | |||||
| const [record,setRecord] = useState([]); | |||||
| const [searchCriteria, setSearchCriteria] = useState({}); | |||||
| const params = useParams(); | |||||
| function getPdfList() { | |||||
| axios.get(`${apiPath}${GET_PDF_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)) { | |||||
| getPdfList(); | |||||
| } | |||||
| }, [searchCriteria]); | |||||
| function applySearch(input) { | |||||
| setSearchCriteria({clientId: params.id, | |||||
| ...input}); | |||||
| } | |||||
| return ( | |||||
| <Grid container rowSpacing={3} columnSpacing={2.75} > | |||||
| <ThemeProvider theme={LIONER_FORM_THEME}> | |||||
| <Grid item xs={12} md={12} lg={12} > | |||||
| <Grid container maxWidth justifyContent="space-between" sx={{mt:-2, width:CARD_MAX_WIDTH}} > | |||||
| <Grid item xs={4} s={4} md={4} lg={4} | |||||
| sx={{ mb: -2.25, display: 'flex', alignItems: 'center'}}> | |||||
| <Typography variant="h4">Search PDF Form</Typography> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| {/* Search Form */} | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <PdfSearchForm | |||||
| // isUpdating={isUpdating} | |||||
| // setIsUpdating={setIsUpdating} | |||||
| applySearch={applySearch} | |||||
| // refTemplateData={refTemplateData} | |||||
| // getTemplateList={getTemplateList} | |||||
| setExpanded={setExpanded} | |||||
| expanded={expanded} | |||||
| clientId={params.id} | |||||
| // userDivision={userDivision} | |||||
| /> | |||||
| </Grid> | |||||
| {!onReady? <LoadingComponent/> : | |||||
| // PDF Table | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <MainCard elevation={0} | |||||
| content={false} | |||||
| sx={{mt:{lg:-1.5}, width: CARD_MAX_WIDTH}} | |||||
| > | |||||
| <div style={{/*height: expanded? '46vh' : '75vh',*/ width: '100%'}}> | |||||
| <PdfTable | |||||
| recordList={record} | |||||
| pageSize={10} | |||||
| expanded={expanded} | |||||
| clientId={params.id} | |||||
| /> | |||||
| </div> | |||||
| </MainCard> | |||||
| </Grid> | |||||
| } | |||||
| </ThemeProvider> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default PdfSearchPage; | |||||
| @@ -10,6 +10,8 @@ import AbilityContext from "../components/AbilityProvider"; | |||||
| // render - login | // render - login | ||||
| const ClientSearchPage = Loadable(lazy( () => import('pages/client/ClientSearchPage'))); | const ClientSearchPage = Loadable(lazy( () => import('pages/client/ClientSearchPage'))); | ||||
| const ClientMaintainPage = Loadable(lazy( () => import('pages/client/ClientMaintainPage'))); | const ClientMaintainPage = Loadable(lazy( () => import('pages/client/ClientMaintainPage'))); | ||||
| const PdfMaintainPage = Loadable(lazy(() => import('pages/pdf/PdfMaintainPage'))); | |||||
| const PdfSearchPage = Loadable(lazy(() => import('pages/pdf/PdfSearchPage'))); | |||||
| // ==============================|| AUTH ROUTING ||============================== // | // ==============================|| AUTH ROUTING ||============================== // | ||||
| @@ -40,6 +42,26 @@ const ClientRoutes =() => { | |||||
| ) | ) | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| path: '/pdf/:id', | |||||
| element: ( | |||||
| handleRouteAbility( | |||||
| ability.can('VIEW', 'DASHBOARD'), | |||||
| <PdfSearchPage />, | |||||
| <Navigate to="/" /> | |||||
| ) | |||||
| ), | |||||
| }, | |||||
| { | |||||
| path: '/pdf/maintain/:id', | |||||
| element: ( | |||||
| handleRouteAbility( | |||||
| ability.can('VIEW', 'DASHBOARD'), | |||||
| <PdfMaintainPage />, | |||||
| <Navigate to="/" /> | |||||
| ) | |||||
| ), | |||||
| }, | |||||
| ] | ] | ||||
| }; | }; | ||||
| }; | }; | ||||
| @@ -10,7 +10,6 @@ import {Navigate} from "react-router"; | |||||
| // render - dashboard | // render - dashboard | ||||
| //const DashboardDefault = Loadable(lazy(() => import('pages/dashboard'))); | //const DashboardDefault = Loadable(lazy(() => import('pages/dashboard'))); | ||||
| const LIONERDashboard = Loadable(lazy(() => import('pages/lionerdashboard'))); | const LIONERDashboard = Loadable(lazy(() => import('pages/lionerdashboard'))); | ||||
| const PDF = Loadable(lazy(() => import('pages/pdf'))); | |||||
| const ReminderPage = Loadable(lazy(() => import('pages/lionerReminderPage'))); | const ReminderPage = Loadable(lazy(() => import('pages/lionerReminderPage'))); | ||||
| const TemplateSearchPage = Loadable(lazy(() => import('pages/lionerSearchPanel'))); | const TemplateSearchPage = Loadable(lazy(() => import('pages/lionerSearchPanel'))); | ||||
| const TemplateMaintainPage = Loadable(lazy(() => import('pages/lionerMaintainSearchTemplatePage'))); | const TemplateMaintainPage = Loadable(lazy(() => import('pages/lionerMaintainSearchTemplatePage'))); | ||||
| @@ -53,16 +52,7 @@ const MainRoutes = () => { | |||||
| ) | ) | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| path: '/pdf', | |||||
| element: ( | |||||
| handleRouteAbility( | |||||
| ability.can('VIEW', 'DASHBOARD'), | |||||
| <PDF />, | |||||
| <Navigate to="/pdf" /> | |||||
| ) | |||||
| ), | |||||
| }, | |||||
| // { | // { | ||||
| // path: '/reminder', | // path: '/reminder', | ||||
| // element: ( | // element: ( | ||||