diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java index d8f96bf..3b6195b 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -95,6 +95,7 @@ import org.bytedeco.opencv.opencv_core.IplImage; import static org.bytedeco.opencv.global.opencv_core.cvFlip; // Java imports for concurrent tasks +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -134,11 +135,13 @@ import BsK.common.packet.req.SaveCheckupRequest; import BsK.common.packet.req.SearchAllRole; import BsK.common.packet.req.SearchAllUser; import BsK.common.packet.req.UploadCheckupImageRequest; +import BsK.common.packet.req.UploadCheckupPdfRequest; import BsK.common.packet.res.AddCheckupResponse; import BsK.common.packet.res.AddPatientResponse; import BsK.common.packet.res.RegisterSuccessResponse; import BsK.common.packet.res.SaveCheckupRes; import BsK.common.packet.res.UploadCheckupImageResponse; +import BsK.common.packet.res.UploadCheckupPdfResponse; import BsK.common.util.date.DateUtils; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; @@ -161,6 +164,7 @@ public class CheckUpPage extends JPanel { private final ResponseListener orderInfoByCheckupListener = this::handleGetOrderInfoByCheckupResponse; private final ResponseListener getAllTemplatesListener = this::handleGetAllTemplatesResponse; private final ResponseListener uploadImageResponseListener = this::handleUploadImageResponse; + private final ResponseListener uploadPdfResponseListener = this::handleUploadPdfResponse; private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField, customerCccdDdcnField; private JTextArea suggestionField, diagnosisField, conclusionField; // Changed symptomsField to suggestionField private JTextPane notesField; @@ -346,6 +350,7 @@ public class CheckUpPage extends JPanel { ClientHandler.addResponseListener(CallPatientResponse.class, callPatientResponseListener); ClientHandler.addResponseListener(GetAllTemplatesRes.class, getAllTemplatesListener); ClientHandler.addResponseListener(UploadCheckupImageResponse.class, uploadImageResponseListener); + ClientHandler.addResponseListener(UploadCheckupPdfResponse.class, uploadPdfResponseListener); // Instantiate the new queue page but don't show it yet queueManagementPage = new QueueManagementPage(); @@ -1684,9 +1689,10 @@ public class CheckUpPage extends JPanel { } // Step 3: If report creation is successful, commit by saving and printing. - if ("ĐANG KHÁM".equals(statusComboBox.getSelectedItem())) { - statusComboBox.setSelectedItem("ĐÃ KHÁM"); - } + // This will be turn on later + // if ("ĐANG KHÁM".equals(statusComboBox.getSelectedItem())) { + // statusComboBox.setSelectedItem("ĐÃ KHÁM"); + // } String statusToSavePrint = (String) statusComboBox.getSelectedItem(); handleSave(); afterSaveActions(statusToSavePrint, "Đã lưu. Đang gửi lệnh in..."); @@ -1698,6 +1704,9 @@ public class CheckUpPage extends JPanel { log.error("Error showing print dialog", e); JOptionPane.showMessageDialog(this, "Không thể hiển thị hộp thoại in: " + e.getMessage(), "Lỗi In", JOptionPane.ERROR_MESSAGE); } + + // Step 5: Upload PDF to backend after successful print dialog + uploadPdfToBackend(ultrasoundResultPrint, "ultrasound_result"); break; case "loupe": // This case now handles "Lưu & Xem" // Step 1: Validate conditions first. @@ -1761,6 +1770,9 @@ public class CheckUpPage extends JPanel { // Step 4: Finally, show the viewer with the pre-made object. JasperViewer.viewReport(jasperPrintToView, false); + + // Step 5: Upload PDF to backend after successful view + uploadPdfToBackend(ultrasoundResultView, "ultrasound_result"); break; } } @@ -3277,6 +3289,67 @@ public class CheckUpPage extends JPanel { } } + private void handleUploadPdfResponse(UploadCheckupPdfResponse response) { + if (response.isSuccess()) { + log.info("Successfully uploaded PDF: {} - {}", response.getPdfType(), response.getMessage()); + SwingUtilities.invokeLater(() -> { + callingStatusLabel.setText("Đã lưu PDF thành công: " + response.getPdfType()); + callingStatusLabel.setForeground(new Color(0, 100, 0)); + }); + } else { + log.error("Failed to upload PDF: {} - {}", response.getPdfType(), response.getMessage()); + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog(this, + "Lỗi khi tải PDF lên server: " + response.getMessage(), + "Lỗi Tải PDF", + JOptionPane.ERROR_MESSAGE); + }); + } + } + + /** + * Uploads PDF to backend asynchronously + * @param ultrasoundResult The UltrasoundResult object to export as PDF + * @param pdfType The type of PDF ("ultrasound_result" or "medserinvoice") + */ + private void uploadPdfToBackend(UltrasoundResult ultrasoundResult, String pdfType) { + if (currentCheckupIdForMedia == null) { + log.warn("No current checkup ID for PDF upload"); + return; + } + + // Run PDF export and upload in background thread to avoid blocking UI + CompletableFuture.runAsync(() -> { + try { + // Export PDF as bytes + byte[] pdfBytes = ultrasoundResult.exportToPdfBytes(); + + // Create filename with override behavior (same name = override) + String fileName = pdfType + ".pdf"; + + // Create and send upload request + UploadCheckupPdfRequest request = new UploadCheckupPdfRequest( + currentCheckupIdForMedia, + pdfBytes, + fileName, + pdfType + ); + + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + log.info("Sent PDF upload request for checkup: {}, type: {}", currentCheckupIdForMedia, pdfType); + + } catch (Exception e) { + log.error("Failed to export or upload PDF for checkup: {}", currentCheckupIdForMedia, e); + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog(this, + "Lỗi khi tạo hoặc tải PDF: " + e.getMessage(), + "Lỗi PDF", + JOptionPane.ERROR_MESSAGE); + }); + } + }); + } + private void handleRecordVideo() { if (currentCheckupMediaPath == null) { JOptionPane.showMessageDialog(this, "Vui lòng chọn một lượt khám để lưu video.", "Chưa chọn lượt khám", JOptionPane.WARNING_MESSAGE); diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java index 5664185..805adf5 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java @@ -29,6 +29,7 @@ import org.apache.pdfbox.rendering.PDFRenderer; // --- JasperReports Imports --- import net.sf.jasperreports.engine.JREmptyDataSource; import net.sf.jasperreports.engine.JasperCompileManager; +import net.sf.jasperreports.engine.JasperExportManager; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import net.sf.jasperreports.engine.JasperReport; @@ -202,6 +203,20 @@ public class UltrasoundResult { } } + /** + * Exports the ultrasound result as PDF bytes for backend upload + * @return byte array of the PDF file + * @throws JRException if there's an error during PDF export + * @throws IOException if there's an error during file operations + */ + public byte[] exportToPdfBytes() throws JRException, IOException { + // First create the JasperPrint object + JasperPrint jasperPrint = createJasperPrint(); + + // Export to PDF bytes + return JasperExportManager.exportReportToPdf(jasperPrint); + } + public void createDialog(JFrame parent) { dialog = new JDialog(parent, "Kết quả siêu âm", true); dialog.setSize(800, 700); diff --git a/src/main/java/BsK/common/packet/req/UploadCheckupPdfRequest.java b/src/main/java/BsK/common/packet/req/UploadCheckupPdfRequest.java new file mode 100644 index 0000000..deb14ce --- /dev/null +++ b/src/main/java/BsK/common/packet/req/UploadCheckupPdfRequest.java @@ -0,0 +1,19 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.Data; + +@Data +public class UploadCheckupPdfRequest implements Packet { + private String checkupId; + private byte[] pdfData; + private String fileName; + private String pdfType; // "ultrasound_result" or "medserinvoice" (for future use) + + public UploadCheckupPdfRequest(String checkupId, byte[] pdfData, String fileName, String pdfType) { + this.checkupId = checkupId; + this.pdfData = pdfData; + this.fileName = fileName; + this.pdfType = pdfType; + } +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/res/UploadCheckupPdfResponse.java b/src/main/java/BsK/common/packet/res/UploadCheckupPdfResponse.java new file mode 100644 index 0000000..619dc74 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/UploadCheckupPdfResponse.java @@ -0,0 +1,19 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; +import lombok.Data; + +@Data +public class UploadCheckupPdfResponse implements Packet { + private boolean success; + private String message; + private String fileName; + private String pdfType; + + public UploadCheckupPdfResponse(boolean success, String message, String fileName, String pdfType) { + this.success = success; + this.message = message; + this.fileName = fileName; + this.pdfType = pdfType; + } +} \ No newline at end of file diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index b85704b..27659a3 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -1260,6 +1260,44 @@ public class ServerHandler extends SimpleChannelInboundHandler { + try { + // Check if Google Drive is connected + if (!Server.isGoogleDriveConnected()) { + log.info("Google Drive not connected - skipping PDF upload for checkup {}", checkupId); + return; + } + + // Get checkup's Google Drive folder ID from database + PreparedStatement checkupStmt = Server.connection.prepareStatement( + "SELECT drive_folder_id FROM Checkup WHERE checkup_id = ?" + ); + checkupStmt.setString(1, checkupId); + ResultSet checkupRs = checkupStmt.executeQuery(); + + String checkupDriveFolderId = ""; + if (checkupRs.next()) { + checkupDriveFolderId = checkupRs.getString("drive_folder_id"); + } + checkupStmt.close(); + checkupRs.close(); + + if (checkupDriveFolderId == null || checkupDriveFolderId.trim().isEmpty()) { + log.warn("Checkup {} has no Google Drive folder ID - cannot upload PDF", checkupId); + return; + } + + log.info("Uploading PDF {} ({}) to Google Drive for checkup {}", fileName, pdfType, checkupId); + + // Create temporary file from byte array for Google Drive upload + java.io.File tempFile = createTempFileFromBytes(pdfData, fileName); + + try { + // Upload file to Google Drive using checkup's folder ID (with override behavior) + String uploadedFileId = Server.getGoogleDriveService().uploadFileToFolder( + checkupDriveFolderId, tempFile, fileName + ); + + log.info("PDF uploaded successfully to Google Drive for checkup {}: FileID={}", checkupId, uploadedFileId); + + // Notify dashboard + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Uploaded PDF %s (%s) to Google Drive for checkup %s", fileName, pdfType, checkupId) + ); + } + + } finally { + // Clean up temporary file + if (tempFile.exists()) { + tempFile.delete(); + log.debug("Cleaned up temporary PDF file: {}", tempFile.getAbsolutePath()); + } + } + + } catch (Exception e) { + log.error("Failed to upload PDF to Google Drive for checkup {}: {}", checkupId, e.getMessage(), e); + + // Notify dashboard of error + if (ServerDashboard.getInstance() != null) { + ServerDashboard.getInstance().addLog( + String.format("Failed to upload PDF %s (%s) to Google Drive for checkup %s: %s", fileName, pdfType, checkupId, e.getMessage()) + ); + } + } + }).start(); + } } diff --git a/src/main/resources/database/BSK.db b/src/main/resources/database/BSK.db index 4f08c2d..75dbbc2 100644 Binary files a/src/main/resources/database/BSK.db and b/src/main/resources/database/BSK.db differ diff --git a/src/main/resources/fonts/ARIALN.TTF b/src/main/resources/fonts/ARIALN.TTF new file mode 100644 index 0000000..49bb696 Binary files /dev/null and b/src/main/resources/fonts/ARIALN.TTF differ diff --git a/src/main/resources/fonts/ARIALNB.TTF b/src/main/resources/fonts/ARIALNB.TTF new file mode 100644 index 0000000..631931e Binary files /dev/null and b/src/main/resources/fonts/ARIALNB.TTF differ diff --git a/src/main/resources/fonts/ARIALNBI.TTF b/src/main/resources/fonts/ARIALNBI.TTF new file mode 100644 index 0000000..4704de2 Binary files /dev/null and b/src/main/resources/fonts/ARIALNBI.TTF differ diff --git a/src/main/resources/fonts/ARIALNI.TTF b/src/main/resources/fonts/ARIALNI.TTF new file mode 100644 index 0000000..1c42b10 Binary files /dev/null and b/src/main/resources/fonts/ARIALNI.TTF differ diff --git a/src/main/resources/fonts/TIMES.TTF b/src/main/resources/fonts/TIMES.TTF new file mode 100644 index 0000000..53ccf4c Binary files /dev/null and b/src/main/resources/fonts/TIMES.TTF differ diff --git a/src/main/resources/fonts/TIMESBD.TTF b/src/main/resources/fonts/TIMESBD.TTF new file mode 100644 index 0000000..3f2987f Binary files /dev/null and b/src/main/resources/fonts/TIMESBD.TTF differ diff --git a/src/main/resources/fonts/TIMESBI.TTF b/src/main/resources/fonts/TIMESBI.TTF new file mode 100644 index 0000000..3f383fb Binary files /dev/null and b/src/main/resources/fonts/TIMESBI.TTF differ diff --git a/src/main/resources/fonts/TIMESI.TTF b/src/main/resources/fonts/TIMESI.TTF new file mode 100644 index 0000000..565a02e Binary files /dev/null and b/src/main/resources/fonts/TIMESI.TTF differ diff --git a/src/main/resources/fonts/arial.ttf b/src/main/resources/fonts/arial.ttf new file mode 100644 index 0000000..810df57 Binary files /dev/null and b/src/main/resources/fonts/arial.ttf differ diff --git a/src/main/resources/fonts/arialbd.ttf b/src/main/resources/fonts/arialbd.ttf new file mode 100644 index 0000000..22040fc Binary files /dev/null and b/src/main/resources/fonts/arialbd.ttf differ diff --git a/src/main/resources/fonts/arialbi.ttf b/src/main/resources/fonts/arialbi.ttf new file mode 100644 index 0000000..0d01c59 Binary files /dev/null and b/src/main/resources/fonts/arialbi.ttf differ diff --git a/src/main/resources/fonts/ariali.ttf b/src/main/resources/fonts/ariali.ttf new file mode 100644 index 0000000..3012ee5 Binary files /dev/null and b/src/main/resources/fonts/ariali.ttf differ diff --git a/src/main/resources/fonts/ariblk.ttf b/src/main/resources/fonts/ariblk.ttf new file mode 100644 index 0000000..e7ae345 Binary files /dev/null and b/src/main/resources/fonts/ariblk.ttf differ diff --git a/src/main/resources/fonts/svn-arial-fonts.xml b/src/main/resources/fonts/svn-arial-fonts.xml new file mode 100644 index 0000000..ccc186c --- /dev/null +++ b/src/main/resources/fonts/svn-arial-fonts.xml @@ -0,0 +1,11 @@ + + + + fonts/SVN-Arial Regular.ttf + fonts/SVN-Arial Bold.ttf + fonts/SVN-Arial Regular.ttf + fonts/SVN-Arial Bold.ttf + Identity-H + true + + \ No newline at end of file diff --git a/src/main/resources/fonts/times-new-roman-fonts.xml b/src/main/resources/fonts/times-new-roman-fonts.xml new file mode 100644 index 0000000..c850697 --- /dev/null +++ b/src/main/resources/fonts/times-new-roman-fonts.xml @@ -0,0 +1,11 @@ + + + + fonts/TIMES.TTF + fonts/TIMESBD.TTF + fonts/TIMESI.TTF + fonts/TIMESBI.TTF + Identity-H + true + + \ No newline at end of file diff --git a/src/main/resources/jasperreports_extension.properties b/src/main/resources/jasperreports_extension.properties new file mode 100644 index 0000000..26aca2b --- /dev/null +++ b/src/main/resources/jasperreports_extension.properties @@ -0,0 +1,2 @@ +net.sf.jasperreports.extension.registry.factory.fonts=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory +net.sf.jasperreports.extension.simple.font.families.timesnewroman=fonts/times-new-roman-fonts.xml \ No newline at end of file