You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

516 lines
19 KiB

  1. // material-ui
  2. import * as React from 'react';
  3. import {apiPath} from "../../../auth/utils";
  4. import { POST_SIG_UPLOAD1 } from "../../../utils/ApiPathConst";
  5. import axios from 'axios';
  6. import {
  7. DataGrid,
  8. GridActionsCellItem,
  9. } from "@mui/x-data-grid";
  10. import EditIcon from '@mui/icons-material/Edit';
  11. import UploadFileIcon from '@mui/icons-material/UploadFile';
  12. import CheckCircleIcon from '@mui/icons-material/CheckCircle';
  13. import FileDownloadIcon from '@mui/icons-material/FileDownload';
  14. import {
  15. Dialog,
  16. DialogTitle,
  17. DialogContent,
  18. DialogActions,
  19. Button,
  20. Typography,
  21. Box,
  22. CircularProgress,
  23. } from '@mui/material';
  24. import {useContext, useEffect} from "react";
  25. import {useNavigate} from "react-router-dom";
  26. // Note: Assuming these utility functions/components are defined elsewhere
  27. import {CustomNoRowsOverlay, dateComparator, getDateString} from "../../../utils/CommonFunction";
  28. import AbilityContext from "../../../components/AbilityProvider";
  29. import {LIONER_BUTTON_THEME} from "../../../themes/colorConst";
  30. import {ThemeProvider} from "@emotion/react";
  31. // ==============================|| PDF TABLE ||============================== //
  32. // Define the structure for the row data stored in state
  33. const initialUploadState = {
  34. id: null,
  35. templateName: null, // This will be the formCode
  36. refType: null, // To differentiate between upload1 and upload2 if needed
  37. }
  38. export default function PdfTable({recordList}) {
  39. const [rows, setRows] = React.useState(recordList);
  40. const [rowModesModel] = React.useState({});
  41. // State for Dialog visibility and Loading state
  42. const [isDialogOpen, setIsDialogOpen] = React.useState(false);
  43. // State to hold the ID, templateName, and refType for the current upload operation
  44. const [currentUploadRow, setCurrentUploadRow] = React.useState(initialUploadState);
  45. const [isUploading, setIsUploading] = React.useState(false);
  46. const navigate = useNavigate()
  47. const ability = useContext(AbilityContext);
  48. const [paginationModel, setPaginationModel] = React.useState({
  49. page: 0,
  50. pageSize:10
  51. });
  52. // Ref for the hidden file input
  53. const fileInputRef = React.useRef(null);
  54. useEffect(() => {
  55. setPaginationModel({page:0,pageSize:10});
  56. setRows(recordList);
  57. }, [recordList]);
  58. const handleEditClick = (id) => () => {
  59. navigate(`/pdf/maintain/${id}`);
  60. };
  61. /**
  62. * Opens the upload dialog and sets the current row details for Upload 1
  63. */
  64. const handleUploadClick = (id, templateName, formCode) => () => {
  65. setCurrentUploadRow({
  66. id: id,
  67. templateName: templateName,
  68. formCode: formCode,
  69. refType: "upload1"
  70. });
  71. setIsDialogOpen(true);
  72. };
  73. /**
  74. * Opens the upload dialog and sets the current row details for Upload 2
  75. */
  76. const handleUpload2Click = (id, templateName, formCode) => () => {
  77. // Placeholder for Upload 2
  78. console.log(`Uploading for row ID ${id} (Upload 2)`);
  79. setCurrentUploadRow({
  80. id: id,
  81. templateName: templateName,
  82. formCode: formCode,
  83. refType: "upload2" // A different refType for a different upload logic/API
  84. });
  85. setIsDialogOpen(true);
  86. };
  87. const handleDownloadClick = (id) => () => {
  88. // 1. Construct the download URL with the ID query parameter
  89. const downloadUrl = `${apiPath}/pdf/download-ff/${id}`;
  90. // Use axios to fetch the PDF as a Blob
  91. axios.get(downloadUrl, {
  92. responseType: 'blob', // IMPORTANT: Tells axios to handle the response as binary data
  93. })
  94. .then((response) => {
  95. // 2. Extract Filename from Content-Disposition Header
  96. const contentDisposition = response.headers['content-disposition'];
  97. let filename = `document-${id}.pdf`; // Fallback filename
  98. if (contentDisposition) {
  99. // Regex to find filename="name.pdf" or filename*=UTF-8''name.pdf
  100. // The server should be setting the filename header correctly.
  101. const filenameMatch = contentDisposition.match(/filename\*?=['"]?([^'"]+)/);
  102. if (filenameMatch && filenameMatch[1]) {
  103. // Decode URI component and remove extra quotes
  104. filename = decodeURIComponent(filenameMatch[1].replace(/\\"/g, ''));
  105. }
  106. }
  107. // 3. Create a temporary anchor tag (<a>) to trigger the download
  108. const blob = new Blob([response.data], { type: 'application/pdf' });
  109. const url = window.URL.createObjectURL(blob);
  110. const link = document.createElement('a');
  111. link.href = url;
  112. link.setAttribute('download', filename);
  113. document.body.appendChild(link);
  114. link.click();
  115. // 4. Clean up
  116. document.body.removeChild(link);
  117. window.URL.revokeObjectURL(url);
  118. })
  119. .catch((error) => {
  120. console.error(`Download failed for ID ${id}:`, error);
  121. // Handle error response (e.g., if the backend returns a 404 or a JSON error)
  122. alert('Failed to download the PDF file. Check server logs.');
  123. });
  124. };
  125. const handleCloseDialog = () => {
  126. setIsDialogOpen(false);
  127. setCurrentUploadRow(initialUploadState); // Reset the current row
  128. if (fileInputRef.current) {
  129. fileInputRef.current.value = ""; // Clear the file input
  130. }
  131. };
  132. // Function to generate the dynamic dialog title
  133. const getUploadDialogTitle = (formCode, refType) => {
  134. console.log("formCode:" + formCode + " refType:" + refType);
  135. if (refType === 'upload1') {
  136. switch (formCode) {
  137. case "IDA":
  138. return "Upload Page 15";
  139. case "FNA":
  140. return "Upload Page 10";
  141. case "HSBCFIN":
  142. return "Upload Page 11";
  143. case "HSBCA31":
  144. return "Upload Page 28-29";
  145. case "MLB03S":
  146. return "Upload Page 9";
  147. case "MLFNA_EN":
  148. return "Upload Page 4";
  149. case "MLFNA_CHI":
  150. return "Upload Page 4";
  151. case "SLGII":
  152. return "Upload Page 13";
  153. case "SLAPP":
  154. return "Upload Page 16";
  155. case "SLFNA_EN":
  156. return "Upload Page 5";
  157. case "SLFNA_CHI":
  158. return "Upload Page 5";
  159. default:
  160. return "Unknown Form";
  161. }
  162. }else if (refType === 'upload2') {
  163. switch (formCode) {
  164. case "MLB03S":
  165. return "Upload Page 13";
  166. case "SLGII":
  167. return "Upload Page 15-16";
  168. case "SLAPP":
  169. return "Upload Page 18-19";
  170. default:
  171. return "Unknown Form";
  172. }
  173. }
  174. // Handle other refTypes if needed, e.g., 'upload2'
  175. if (refType === 'upload2') {
  176. return `Upload Template 2 for ${formCode}`;
  177. }
  178. return "Upload File"; // Fallback
  179. };
  180. // Function to handle file selection and API submission
  181. const handleFileChange = async (event) => {
  182. const file = event.target.files[0];
  183. const { id, templateName, refType } = currentUploadRow;
  184. if (!file || !id || !refType) return;
  185. if (file.type !== "application/pdf") {
  186. alert("Please select a PDF file.");
  187. event.target.value = "";
  188. return;
  189. }
  190. // The URL should potentially change based on refType if upload2 uses a different endpoint
  191. const uploadUrl = `${apiPath}${POST_SIG_UPLOAD1}`; // Assuming POST_SIG_UPLOAD1 is used for both for now
  192. // 1. Create FormData
  193. const formData = new FormData();
  194. formData.append('file', file);
  195. formData.append('refId', id);
  196. formData.append('refType', refType); // Pass the refType to the backend
  197. setIsUploading(true);
  198. try {
  199. const response = await axios.post(
  200. uploadUrl,
  201. formData,
  202. {
  203. headers: {
  204. 'Content-Type': 'multipart/form-data',
  205. }
  206. }
  207. );
  208. if (response.status === 200) {
  209. alert('Upload success');
  210. console.log(`PDF file ${file.name} successfully uploaded for record ID: ${id} with refType: ${refType}!`);
  211. // --- START: Update local state to show the green tick ---
  212. const uploadedFileId = response.data?.fileId || 'temp-id-' + Date.now(); // Assume the response has a fileId or use a temp one
  213. setRows(prevRows =>
  214. prevRows.map(row => {
  215. if (row.id === id) {
  216. // Update the relevant file ID field based on refType
  217. const updateField = refType === 'upload1' ? 'upload1FileId' : 'upload2FileId';
  218. return {
  219. ...row,
  220. [updateField]: uploadedFileId // Set the file ID to trigger the icon
  221. };
  222. }
  223. return row;
  224. })
  225. );
  226. // --- END: Update local state to show the green tick ---
  227. } else {
  228. throw new Error(`Upload failed with status: ${response.status}`);
  229. }
  230. } catch (error) {
  231. console.error('Upload error:', error);
  232. // Check if the error has a response and use its message if available
  233. const errorMessage = error.response?.data?.message || error.message;
  234. alert(`Error uploading file: ${errorMessage}`);
  235. } finally {
  236. // 3. Cleanup and close
  237. setIsUploading(false);
  238. handleCloseDialog();
  239. }
  240. };
  241. const handleChooseFile = () => {
  242. // Trigger the hidden file input click
  243. if (fileInputRef.current) {
  244. fileInputRef.current.click();
  245. }
  246. };
  247. const columns = [
  248. {
  249. field: 'actions',
  250. type: 'actions',
  251. headerName: 'Edit',
  252. width: 100,
  253. cellClassName: 'actions',
  254. getActions: ({id}) => {
  255. return [
  256. <ThemeProvider key="OutSave" theme={LIONER_BUTTON_THEME}>
  257. <GridActionsCellItem
  258. icon={<EditIcon sx={{fontSize: 25}}/>}
  259. label="Edit"
  260. className="textPrimary"
  261. onClick={handleEditClick(id)}
  262. color="edit"
  263. />
  264. </ThemeProvider>
  265. ]
  266. },
  267. },
  268. {
  269. id: 'templateName',
  270. field: 'templateName',
  271. headerName: 'Form Name',
  272. flex: 2,
  273. renderCell: (params) => {
  274. return (
  275. <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}>
  276. {params.value} {params.row.vNum}
  277. </div>
  278. );
  279. }
  280. },
  281. {
  282. field: 'upload1',
  283. type: 'actions',
  284. // Multi-line header
  285. headerName: (
  286. <div>
  287. Upload Sig.
  288. </div>
  289. ),
  290. width: 100,
  291. cellClassName: 'actions',
  292. getActions: ({ id, row }) => {
  293. // Check if a file ID exists to determine if a file is present for Upload 1
  294. const isUploaded1 = !!row.upload1FileId;
  295. const isUploaded2 = !!row.upload2FileId; // <<< ADD THIS LINE <<<
  296. const templateName = row.templateName;
  297. const formCode = row.formCode;
  298. // Determine the icon and label based on upload status for Upload 1
  299. const upload1Icon = isUploaded1
  300. ? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
  301. : <UploadFileIcon sx={{fontSize: 25}}/>; // Upload icon if not uploaded
  302. const upload1Label = isUploaded1 ? "Update Signature" : "Upload Signature";
  303. // Define the actions
  304. const actions = [
  305. <ThemeProvider key="UploadSign1" theme={LIONER_BUTTON_THEME}>
  306. <GridActionsCellItem
  307. icon={upload1Icon} // Use the dynamic icon
  308. label={upload1Label} // Use the dynamic label
  309. className="textPrimary"
  310. onClick={handleUploadClick(id, templateName, formCode)} // Pass templateName here
  311. color="upload" // Use 'upload' color which will apply to the button
  312. />
  313. </ThemeProvider>
  314. ];
  315. // Conditional rendering logic for Upload 2
  316. if (row.formCode === "MLB03S" || row.formCode === "SLGII" || row.formCode === "SLAPP") {
  317. // Determine the icon and label based on upload status for Upload 2 <<< START CHANGES HERE <<<
  318. const upload2Icon = isUploaded2
  319. ? <CheckCircleIcon sx={{fontSize: 25, color: 'success.main'}} /> // Green tick if uploaded
  320. : <UploadFileIcon sx={{fontSize: 25}}/>; // Upload icon if not uploaded
  321. const upload2Label = isUploaded2 ? "Update 2" : "Upload 2";
  322. // >>> END CHANGES HERE <<<
  323. actions.push(
  324. <ThemeProvider key="UploadSign2" theme={LIONER_BUTTON_THEME}>
  325. <GridActionsCellItem
  326. icon={upload2Icon} // <<< USE DYNAMIC ICON <<<
  327. label={upload2Label} // <<< USE DYNAMIC LABEL <<<
  328. className="textPrimary"
  329. onClick={handleUpload2Click(id, templateName, formCode)} // Pass templateName here
  330. color="upload"
  331. />
  332. </ThemeProvider>
  333. );
  334. }
  335. return actions;
  336. },
  337. },
  338. {
  339. field: 'actions2',
  340. type: 'actions',
  341. headerName: 'Download',
  342. width: 100,
  343. cellClassName: 'actions',
  344. getActions: ({id}) => {
  345. return [
  346. <ThemeProvider key="DownloadFile" theme={LIONER_BUTTON_THEME}>
  347. <GridActionsCellItem
  348. icon={<FileDownloadIcon sx={{fontSize: 25}}/>}
  349. label="Download"
  350. className="textPrimary"
  351. onClick={handleDownloadClick(id)}
  352. color="download"
  353. />
  354. </ThemeProvider>
  355. ]
  356. },
  357. },
  358. {
  359. id: 'createDate',
  360. field: 'createDate',
  361. headerName: 'Create Datetime',
  362. flex: 1,
  363. sortComparator: dateComparator,
  364. renderCell: (params) => (
  365. <div>
  366. {getDateString(params.row.created, 'dd/MM/yyyy HH:mm:ss')}
  367. </div>
  368. ),
  369. },
  370. {
  371. id: 'version',
  372. field: 'version',
  373. headerName: 'Version',
  374. flex: 0.5,
  375. renderCell: (params) => {
  376. return (
  377. <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}>
  378. {params.value}
  379. </div>
  380. );
  381. }
  382. },
  383. {
  384. id: 'modified',
  385. field: 'modified',
  386. headerName: 'Modified Date',
  387. flex: 1,
  388. sortComparator: dateComparator,
  389. renderCell: (params) => (
  390. <div>
  391. {getDateString(params.row.modified, 'dd/MM/yyyy HH:mm:ss')}
  392. </div>
  393. ),
  394. },
  395. ];
  396. return (
  397. <div>
  398. <DataGrid
  399. rows={rows}
  400. columns={columns}
  401. // Increased height to accommodate the multi-line header
  402. columnHeaderHeight={70}
  403. editMode="row"
  404. rowModesModel={rowModesModel}
  405. getRowHeight={() => 'auto'}
  406. paginationModel={paginationModel}
  407. onPaginationModelChange={setPaginationModel}
  408. slots={{
  409. noRowsOverlay: () => (
  410. CustomNoRowsOverlay()
  411. )
  412. }}
  413. pageSizeOptions={[10]}
  414. autoHeight
  415. />
  416. {/* The Upload Dialog Box */}
  417. <Dialog
  418. open={isDialogOpen}
  419. onClose={handleCloseDialog}
  420. fullWidth
  421. maxWidth="sm"
  422. // Prevent closing when upload is active
  423. disableEscapeKeyDown={isUploading}
  424. TransitionProps={{ onExited: handleCloseDialog }}
  425. >
  426. <DialogTitle>
  427. {/* Dynamic Title based on currentUploadRow state */}
  428. **{getUploadDialogTitle(currentUploadRow.formCode, currentUploadRow.refType)}**
  429. </DialogTitle>
  430. <DialogContent dividers>
  431. <Box sx={{ mt: 2, textAlign: 'center' }}>
  432. {/* Button to trigger file selection */}
  433. <Button
  434. variant="contained"
  435. color="primary"
  436. startIcon={isUploading ? <CircularProgress size={20} color="inherit" /> : <UploadFileIcon />}
  437. onClick={handleChooseFile}
  438. disabled={isUploading}
  439. >
  440. {isUploading ? 'Uploading...' : 'Choose PDF File'}
  441. </Button>
  442. {/* Hidden File Input */}
  443. <input
  444. ref={fileInputRef}
  445. type="file"
  446. accept="application/pdf"
  447. onChange={handleFileChange}
  448. style={{ display: 'none' }}
  449. disabled={isUploading}
  450. />
  451. {/* Display the selected file name if a file is chosen */}
  452. {fileInputRef.current?.files[0] && (
  453. <Typography variant="body2" sx={{ mt: 1 }}>
  454. Selected: **{fileInputRef.current.files[0].name}**
  455. </Typography>
  456. )}
  457. </Box>
  458. </DialogContent>
  459. <DialogActions>
  460. <Button onClick={handleCloseDialog} color="inherit" disabled={isUploading}>
  461. Cancel
  462. </Button>
  463. </DialogActions>
  464. </Dialog>
  465. </div>
  466. );
  467. }