選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

PdfTable.js 19 KiB

2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1週間前
2ヶ月前
1週間前
2ヶ月前
1週間前
2ヶ月前
1週間前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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. }