diff --git a/config/config.properties b/config/config.properties index c4b479c..714f407 100644 --- a/config/config.properties +++ b/config/config.properties @@ -1,13 +1,13 @@ #BSK Clinic Management System Configuration - Updated via Settings Dialog -#Sat Aug 02 11:33:13 ICT 2025 -clinic.address=So ABC, Duong XYZ, Quan 1, TP.HCM -clinic.name=Phong kham BSK -clinic.phone=0123456789 -clinic.prefix=BSK +#Tue Aug 05 09:52:46 ICT 2025 +app.auto_change_status_to_finished=false +clinic.address=112B/1 Khu ph\u1ED1 2, x\u00E3 \u0110\u1EE9c H\u00F2a, t\u1EC9nh T\u00E2y Ninh +clinic.name=BS. NGUY\u1EC4N TH\u1ECA KH\u1EAEP +clinic.phone=0908410007 +clinic.prefix=PH\u00D2NG KH\u00C1M S\u1EA2N PH\u1EE4 KHOA google.drive.enabled=true google.drive.root.folder=BSK_Clinic_Patient_Files server.address=127.0.0.1 server.port=1999 storage.checkup_media_base_dir=image/checkup_media storage.ultrasound_folder_path=ANH SIEU AM -app.auto_change_status_to_finished=false \ No newline at end of file diff --git a/database/BSK.db b/database/BSK.db index 22cc969..c8e4bcd 100644 Binary files a/database/BSK.db and b/database/BSK.db differ diff --git a/database/BSK.db-shm b/database/BSK.db-shm index 70df02f..1d896bc 100644 Binary files a/database/BSK.db-shm and b/database/BSK.db-shm differ diff --git a/database/BSK.db-wal b/database/BSK.db-wal index 452d201..c644184 100644 Binary files a/database/BSK.db-wal and b/database/BSK.db-wal differ diff --git a/image/checkup_media/4/ultrasound_4_20250805_095053_712.jpg b/image/checkup_media/4/ultrasound_4_20250805_095053_712.jpg new file mode 100644 index 0000000..b034328 Binary files /dev/null and b/image/checkup_media/4/ultrasound_4_20250805_095053_712.jpg differ diff --git a/image/checkup_media/4/ultrasound_4_20250805_095206_513.jpg b/image/checkup_media/4/ultrasound_4_20250805_095206_513.jpg new file mode 100644 index 0000000..c3b954e Binary files /dev/null and b/image/checkup_media/4/ultrasound_4_20250805_095206_513.jpg differ diff --git a/image/checkup_media/6/IMG_6_20250806_014621.jpg b/image/checkup_media/6/IMG_6_20250806_014621.jpg new file mode 100644 index 0000000..a69aa9c Binary files /dev/null and b/image/checkup_media/6/IMG_6_20250806_014621.jpg differ diff --git a/image/checkup_media/6/IMG_6_20250806_111037.jpg b/image/checkup_media/6/IMG_6_20250806_111037.jpg new file mode 100644 index 0000000..b6a36e5 Binary files /dev/null and b/image/checkup_media/6/IMG_6_20250806_111037.jpg differ diff --git a/pom.xml b/pom.xml index c1073c2..8ad3f8f 100644 --- a/pom.xml +++ b/pom.xml @@ -246,5 +246,12 @@ sqlite-jdbc 3.45.1.0 + + com.yourcompany + jasper-fonts-timesnewroman + 1.0 + system + ${project.basedir}/src/main/resources/fonts/timesnewroman-fonts.jar + \ No newline at end of file diff --git a/src/main/java/BsK/client/LocalStorage.java b/src/main/java/BsK/client/LocalStorage.java index f749397..746c4b4 100644 --- a/src/main/java/BsK/client/LocalStorage.java +++ b/src/main/java/BsK/client/LocalStorage.java @@ -27,6 +27,7 @@ public class LocalStorage { public static Date dataDialogToDate = new Date(); public static String dataDialogDoctorName = "Tất cả"; public static String dataDialogIdSearchTerm = ""; + public static int unreadMessageCnt = 0; public static List doctorsName = new ArrayList<>(); public static String[] provinces = new String[]{}; 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 e49b0cf..89ba94a 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -1929,8 +1929,6 @@ public class CheckUpPage extends JPanel { 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", checkupIdField.getText()); afterSaveActions(statusToSavePrint, "Đã lưu. Đang gửi lệnh in..."); break; case "loupe": // This case now handles "Lưu & Xem" @@ -2004,8 +2002,6 @@ 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", checkupIdField.getText()); afterSaveActions(statusToSaveView, "Đã lưu. Đang tạo bản xem trước..."); break; } @@ -3700,44 +3696,6 @@ public class CheckUpPage extends JPanel { } } - /** - * 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, String checkupId) { - log.info("uploadPdfToBackend"); - // 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( - checkupId, - pdfBytes, - fileName, - pdfType - ); - - NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); - log.info("Sent PDF upload request for checkup: {}, type: {}", checkupId, pdfType); - - } catch (Exception e) { - log.error("Failed to export or upload PDF for checkup: {}", checkupId, 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) { @@ -3765,28 +3723,25 @@ public class CheckUpPage extends JPanel { } catch (ClassNotFoundException e) { throw new Exception("Không tìm thấy thư viện JavaCV cần thiết. Vui lòng kiểm tra cài đặt Maven.", e); } - + // Initialize frame converter frameConverter = new Java2DFrameConverter(); - + // Get webcam's actual frame rate double webcamFPS = selectedWebcam.getFPS(); - // If webcam doesn't report FPS or reports an invalid value, default to 15 + // If webcam doesn't report FPS or reports an invalid value, default to a base value like 15 if (webcamFPS <= 0 || Double.isNaN(webcamFPS)) { webcamFPS = 15.0; } - // captureRate is used by the capture loop logic for polling interval - final double captureRate = webcamFPS; - - // If observed playback is 2x faster, the actual sustainable FPS for the recorder - // should be half the reported/assumed FPS. - double recorderFrameRate = webcamFPS / 2.0; - if (recorderFrameRate < 1.0) { // Ensure a minimum frame rate of 1.0 - recorderFrameRate = 1.0; - } - log.info("Setting recorder frame rate to: {} (derived from webcam FPS: {})", String.format("%.2f", recorderFrameRate), String.format("%.2f", webcamFPS)); - + // FIX FOR TIMING ISSUE: We double the reported FPS to match the actual frame delivery speed. + final double recorderFrameRate = webcamFPS * 2.0; + + // The capture loop will now also target this corrected frame rate to avoid missing frames. + final double captureRate = recorderFrameRate; + + log.info("Setting recorder frame rate to: {} (Corrected from webcam's reported FPS of {})", String.format("%.2f", recorderFrameRate), String.format("%.2f", webcamFPS)); + // Initialize the recorder with optimized settings try { Dimension size = selectedWebcam.getViewSize(); @@ -3795,23 +3750,24 @@ public class CheckUpPage extends JPanel { // Video format and codec settings recorder.setVideoCodec(org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264); recorder.setFormat("mp4"); - // Use the adjusted effective frame rate for the recorder - recorder.setFrameRate(recorderFrameRate); - recorder.setVideoQuality(1); - // Pixel format and color space settings + // Set the corrected frame rate + recorder.setFrameRate(recorderFrameRate); + + // Pixel format recorder.setPixelFormat(org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P); - // Encoding settings for better performance - recorder.setVideoOption("preset", "medium"); + // --- QUALITY SETTINGS --- + // Using CRF is the best way to control quality for H.264. + // The setVideoQuality(0) call is removed to avoid conflicts. + recorder.setVideoOption("crf", "18"); // High quality + recorder.setVideoOption("preset", "slow"); // Better compression recorder.setVideoOption("tune", "zerolatency"); - recorder.setVideoOption("crf", "23"); - // Buffer size settings - increased for smoother recording - recorder.setVideoOption("bufsize", "5000k"); - recorder.setVideoOption("maxrate", "5000k"); + // Buffer size settings + recorder.setVideoOption("bufsize", "20000k"); + recorder.setVideoOption("maxrate", "20000k"); - // Remove the speed filter and start recorder recorder.start(); } catch (Exception e) { throw new Exception("Lỗi khởi tạo FFmpeg recorder: " + e.getMessage(), e); diff --git a/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java b/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java index 06d4b24..685e90f 100644 --- a/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java +++ b/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java @@ -721,7 +721,7 @@ public class AddDialog extends JDialog { // Validate required fields if (patientName.isEmpty() || patientPhone.isEmpty() || customerAddress.isEmpty() || - dobPicker.getJFormattedTextField().getText().isEmpty()) { + dobPicker.getJFormattedTextField().getText().isEmpty() || cccd.isEmpty()) { JOptionPane.showMessageDialog(this, "Vui lòng điền đầy đủ thông tin"); return; } diff --git a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java index 4f9c9f2..4a82a03 100644 --- a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java +++ b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java @@ -271,7 +271,7 @@ public class MedicineDialog extends JDialog { routeField = new JTextField(20); routeField.setFont(textFont); routeField.setPreferredSize(textFieldSize); - routeField.setEditable(false); + routeField.setEditable(true); medicineInfoPanel.add(routeField, gbc); gbc.weightx = 0.0; // --- END ADDED --- diff --git a/src/main/java/BsK/client/ui/component/common/NavBar.java b/src/main/java/BsK/client/ui/component/common/NavBar.java index 7105dc5..038173f 100644 --- a/src/main/java/BsK/client/ui/component/common/NavBar.java +++ b/src/main/java/BsK/client/ui/component/common/NavBar.java @@ -31,6 +31,8 @@ public class NavBar extends JPanel { private JLabel activeNavItem = null; private final MainFrame mainFrame; + private JLabel unreadCountLabel; // NEW: Label to display unread count + private RoundedPanel messageButtonPanel; // NEW: Reference to message button panel for repainting // Listener for incoming emergency alerts private final ResponseListener emergencyResponseListener = this::handleEmergencyResponse; @@ -275,6 +277,10 @@ public class NavBar extends JPanel { private void showNotificationForMessage(SimpleMessageResponse response) { SwingUtilities.invokeLater(() -> { + // Increment unread message count + LocalStorage.unreadMessageCnt++; + updateUnreadCountDisplay(); + // Create a borderless window for the notification JWindow notificationWindow = new JWindow(mainFrame); @@ -336,6 +342,26 @@ public class NavBar extends JPanel { log.info("NavBar message listener re-enabled."); } + // NEW: Method to reset unread count when chat dialog opens + public void resetUnreadMessageCount() { + LocalStorage.unreadMessageCnt = 0; + updateUnreadCountDisplay(); + log.info("Unread message count reset to 0."); + } + + // NEW: Method to update the unread count display + private void updateUnreadCountDisplay() { + SwingUtilities.invokeLater(() -> { + if (LocalStorage.unreadMessageCnt > 0) { + unreadCountLabel.setText(String.valueOf(LocalStorage.unreadMessageCnt)); + unreadCountLabel.setVisible(true); + } else { + unreadCountLabel.setVisible(false); + } + messageButtonPanel.repaint(); + }); + } + private Component createEmergencyButton() { Color emergencyColor = new Color(220, 38, 38); RoundedPanel buttonPanel = new RoundedPanel(15, emergencyColor, true); @@ -435,22 +461,23 @@ public class NavBar extends JPanel { private Component createMessageButton() { Color messageColor = new Color(150, 150, 150); // Muted gray color - RoundedPanel buttonPanel = new RoundedPanel(15, messageColor, true); - buttonPanel.setLayout(new BorderLayout(2, 2)); - buttonPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - buttonPanel.setToolTipText("Tin nhắn"); // Add a tooltip + messageButtonPanel = new RoundedPanel(15, messageColor, true); // Store reference + messageButtonPanel.setLayout(null); // Use null layout for absolute positioning + messageButtonPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + messageButtonPanel.setToolTipText("Tin nhắn"); // Add a tooltip // Make the button smaller and square-like - buttonPanel.setPreferredSize(new Dimension(55, 55)); - buttonPanel.setBorder(BorderFactory.createCompoundBorder( + messageButtonPanel.setPreferredSize(new Dimension(55, 55)); + messageButtonPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(2, 2, 2, 2), BorderFactory.createEmptyBorder(5, 10, 5, 10) )); - // Create the label without text + // Create the main icon label JLabel buttonLabel = new JLabel(); buttonLabel.setHorizontalAlignment(SwingConstants.CENTER); buttonLabel.setOpaque(false); + buttonLabel.setBounds(0, 0, 55, 55); // Position the main icon try { String iconPath = "src/main/java/BsK/client/ui/assets/icon/chat.png"; @@ -463,11 +490,33 @@ public class NavBar extends JPanel { buttonLabel.setText("Msg"); // Fallback text if icon fails } - buttonPanel.add(buttonLabel, BorderLayout.CENTER); + // Create the unread count badge + unreadCountLabel = new JLabel(); + unreadCountLabel.setOpaque(true); + unreadCountLabel.setBackground(Color.RED); + unreadCountLabel.setForeground(Color.WHITE); + unreadCountLabel.setFont(new Font("Arial", Font.BOLD, 10)); + unreadCountLabel.setHorizontalAlignment(SwingConstants.CENTER); + unreadCountLabel.setBounds(35, 5, 18, 18); // Position in top-right corner + unreadCountLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + + // Make the badge circular + unreadCountLabel.setPreferredSize(new Dimension(18, 18)); + unreadCountLabel.setMinimumSize(new Dimension(18, 18)); + unreadCountLabel.setMaximumSize(new Dimension(18, 18)); + + // Initially hide the badge + unreadCountLabel.setVisible(false); - buttonPanel.addMouseListener(new MouseAdapter() { + messageButtonPanel.add(buttonLabel); + messageButtonPanel.add(unreadCountLabel); + + messageButtonPanel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { + // Reset unread count when opening chat + resetUnreadMessageCount(); + // Open the SimpleChatDialog SimpleChatDialog chatDialog = new SimpleChatDialog(mainFrame, NavBar.this); chatDialog.setVisible(true); @@ -475,16 +524,16 @@ public class NavBar extends JPanel { @Override public void mouseEntered(MouseEvent e) { - buttonPanel.setBackground(messageColor.darker()); + messageButtonPanel.setBackground(messageColor.darker()); } @Override public void mouseExited(MouseEvent e) { - buttonPanel.setBackground(messageColor); + messageButtonPanel.setBackground(messageColor); } }); - return buttonPanel; + return messageButtonPanel; } /** diff --git a/src/main/java/BsK/server/ServerDashboard.java b/src/main/java/BsK/server/ServerDashboard.java index efcba66..b3da0f0 100644 --- a/src/main/java/BsK/server/ServerDashboard.java +++ b/src/main/java/BsK/server/ServerDashboard.java @@ -5,6 +5,8 @@ import javax.swing.border.TitledBorder; import javax.swing.table.DefaultTableModel; import javax.swing.text.*; import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.OperatingSystemMXBean; @@ -35,8 +37,6 @@ public class ServerDashboard extends JFrame { private boolean isAutoScrollEnabled = true; private JTable networkTable; private DefaultTableModel networkTableModel; - - // --- NEW --- private JButton backupDbButton; public static ServerDashboard getInstance() { @@ -49,8 +49,11 @@ public class ServerDashboard extends JFrame { private ServerDashboard() { setTitle("BSK Server Dashboard"); setSize(1200, 800); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); + + // --- MODIFICATION START: Add custom close logic --- + addCustomCloseListener(); + // --- MODIFICATION END --- // Main container panel JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); @@ -66,7 +69,12 @@ public class ServerDashboard extends JFrame { startSystemStatsTimer(); startNetworkStatsTimer(); } - + + // Unchanged methods from your original code are here... + // createHeaderPanel(), performDatabaseBackup(), createMainContentPanel(), etc. + // I've omitted them for brevity but they should remain in your file. + + // --- START OF UNCHANGED METHODS (for context) --- private JPanel createHeaderPanel() { JPanel headerPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); @@ -127,7 +135,6 @@ public class ServerDashboard extends JFrame { return headerPanel; } - // --- NEW: Method to handle the backup action --- private void performDatabaseBackup() { // Disable button to prevent multiple clicks backupDbButton.setEnabled(false); @@ -153,10 +160,10 @@ public class ServerDashboard extends JFrame { // Update UI on failure SwingUtilities.invokeLater(() -> { addLog("❌ Database backup failed: " + e.getMessage()); - JOptionPane.showMessageDialog(this, - "Failed to backup the database.\nError: " + e.getMessage() + "\n\nCheck logs for more details.", - "Backup Failed", - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, + "Failed to backup the database.\nError: " + e.getMessage() + "\n\nCheck logs for more details.", + "Backup Failed", + JOptionPane.ERROR_MESSAGE); }); } finally { // Always re-enable the button on the UI thread @@ -166,7 +173,6 @@ public class ServerDashboard extends JFrame { } private JSplitPane createMainContentPanel() { - // ... (This method remains unchanged) // --- Log Panel --- JPanel logPanel = new JPanel(new BorderLayout()); logPanel.setBorder(BorderFactory.createTitledBorder("Server Logs")); @@ -197,7 +203,6 @@ public class ServerDashboard extends JFrame { } private JPanel createLogControlsPanel() { - // ... (This method remains unchanged) JPanel logControlsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); searchField = new JTextField(20); JButton searchButton = new JButton("Search"); @@ -218,7 +223,6 @@ public class ServerDashboard extends JFrame { } private void startSystemStatsTimer() { - // ... (This method remains unchanged) MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); @@ -240,13 +244,11 @@ public class ServerDashboard extends JFrame { } private void startNetworkStatsTimer() { - // ... (This method remains unchanged) networkStatsTimer = new Timer(1000, e -> refreshNetworkTable()); networkStatsTimer.start(); } public void refreshNetworkTable() { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { networkTableModel.setRowCount(0); for (ClientConnection conn : SessionManager.getAllConnections()) { @@ -263,7 +265,6 @@ public class ServerDashboard extends JFrame { } private void searchInLogs() { - // ... (This method remains unchanged) String searchText = searchField.getText().toLowerCase(); if (searchText.isEmpty()) { return; @@ -288,7 +289,6 @@ public class ServerDashboard extends JFrame { } private void clearLogs() { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { try { logArea.getDocument().remove(0, logArea.getDocument().getLength()); @@ -300,7 +300,6 @@ public class ServerDashboard extends JFrame { } public void updateStatus(String status, Color color) { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { statusLabel.setText("Status: " + status); statusLabel.setForeground(color); @@ -308,26 +307,22 @@ public class ServerDashboard extends JFrame { } public void updatePort(int port) { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> portLabel.setText("Port: " + port) ); } public static void incrementClients() { - // ... (This method remains unchanged) connectedClients++; updateClientCount(); } public static void decrementClients() { - // ... (This method remains unchanged) connectedClients = Math.max(0, connectedClients - 1); updateClientCount(); } private static void updateClientCount() { - // ... (This method remains unchanged) if (instance != null) { SwingUtilities.invokeLater(() -> { instance.clientsLabel.setText("Connected Clients: " + connectedClients); @@ -337,7 +332,6 @@ public class ServerDashboard extends JFrame { } public void addLog(String message) { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { try { Document doc = logArea.getDocument(); @@ -360,7 +354,6 @@ public class ServerDashboard extends JFrame { } public void updateGoogleDriveStatus(boolean connected, String statusMessage) { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { if (connected) { googleDriveLabel.setText("✅ " + statusMessage); @@ -377,7 +370,6 @@ public class ServerDashboard extends JFrame { } private void retryGoogleDriveConnection() { - // ... (This method remains unchanged) SwingUtilities.invokeLater(() -> { googleDriveLabel.setText("🔄 Connecting..."); googleDriveLabel.setForeground(Color.ORANGE); @@ -413,7 +405,6 @@ public class ServerDashboard extends JFrame { @Override public void dispose() { - // ... (This method remains unchanged) if (statsTimer != null) { statsTimer.stop(); } @@ -422,4 +413,45 @@ public class ServerDashboard extends JFrame { } super.dispose(); } + // --- END OF UNCHANGED METHODS --- + + // --- MODIFICATION START: Add new methods for close confirmation --- + /** + * Overrides the default close operation to show a confirmation dialog. + */ + private void addCustomCloseListener() { + // This tells the frame to do nothing automatically when the 'X' is clicked. + // We will handle the closing manually. + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + showExitConfirmationDialog(); + } + }); + } + + /** + * Displays the confirmation dialog and exits the application if confirmed. + */ + private void showExitConfirmationDialog() { + String title = "Xác nhận thoát"; + String message = "Nếu tắt bạn sẽ đóng tất cả các máy con. Bạn có chắc chắn không?"; + + int result = JOptionPane.showConfirmDialog( + this, + message, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + + if (result == JOptionPane.YES_OPTION) { + // User confirmed, so exit the entire application. + // This will trigger the graceful shutdown logic in your Server's main() method. + System.exit(0); + } + // If the user chooses "No", the dialog closes and nothing else happens. + } + // --- MODIFICATION END --- } \ 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 8575195..721ee39 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -1168,43 +1168,6 @@ public class ServerHandler extends SimpleChannelInboundHandler { - try { - if (!Server.isGoogleDriveConnected()) { - log.info("Google Drive not connected - skipping PDF upload for checkup {}", checkupId); - return; - } - - // --- SỬA LỖI: Lấy thông tin drive_folder_id từ CSDL một cách an toàn --- - String checkupDriveFolderId = null; - String sql = "SELECT drive_folder_id FROM Checkup WHERE checkup_id = ?"; - - try (Connection conn = DatabaseManager.getConnection(); - PreparedStatement stmt = conn.prepareStatement(sql)) { - - // Giả định checkup_id trong CSDL là kiểu số (integer) - stmt.setInt(1, Integer.parseInt(checkupId)); - - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - checkupDriveFolderId = rs.getString("drive_folder_id"); - } - } - } - - 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); - - // Logic tạo và dọn dẹp file tạm của bạn đã tốt, chúng ta giữ lại - java.io.File tempFile = null; - try { - tempFile = createTempFileFromBytes(pdfData, fileName); - - String uploadedFileId = Server.getGoogleDriveService().uploadFileToFolder( - checkupDriveFolderId, tempFile, fileName - ); - - log.info("PDF uploaded successfully to Google Drive for checkup {}: FileID={}", checkupId, uploadedFileId); - - if (ServerDashboard.getInstance() != null) { - ServerDashboard.getInstance().addLog( - String.format("Uploaded PDF %s (%s) to Google Drive for checkup %s", fileName, pdfType, checkupId) - ); - } - } finally { - if (tempFile != null && tempFile.exists()) { - if (!tempFile.delete()) { - log.warn("Could not delete temporary PDF file: {}", tempFile.getAbsolutePath()); - } - } - } - - } catch (NumberFormatException e) { - log.error("Invalid checkupId format '{}' for PDF upload.", checkupId); - } catch (Exception e) { - log.error("Failed to upload PDF to Google Drive for checkup {}: {}", checkupId, e.getMessage(), e); - if (ServerDashboard.getInstance() != null) { - ServerDashboard.getInstance().addLog( - String.format("Failed to upload PDF %s (%s) for checkup %s: %s", fileName, pdfType, checkupId, e.getMessage()) - ); - } - } - }); - } - + private void broadcastQueueUpdate() { // SỬA ĐỔI: Sử dụng một kết nối duy nhất, an toàn cho tất cả các truy vấn bên trong try (Connection conn = DatabaseManager.getConnection()) { diff --git a/src/main/resources/print_forms/ultrasoundresult.jrxml b/src/main/resources/print_forms/ultrasoundresult.jrxml index c8c1d3e..402865c 100644 --- a/src/main/resources/print_forms/ultrasoundresult.jrxml +++ b/src/main/resources/print_forms/ultrasoundresult.jrxml @@ -51,55 +51,49 @@ - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -108,31 +102,31 @@ - + - + - + - + - - + + - + - + - + - + @@ -249,14 +243,14 @@ - + - + - + diff --git a/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml b/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml index 5f9e1f6..6f320f6 100644 --- a/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml +++ b/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml @@ -56,55 +56,49 @@ - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -113,31 +107,31 @@ - + - + - + - - + + - + - + - + - + @@ -163,10 +157,10 @@ - + - + diff --git a/temp/barcodes/invoice_barcode.png b/temp/barcodes/invoice_barcode.png index caa8d35..e407d72 100644 Binary files a/temp/barcodes/invoice_barcode.png and b/temp/barcodes/invoice_barcode.png differ diff --git a/temp/qrcodes/invoice_qrcode.png b/temp/qrcodes/invoice_qrcode.png index 776d23a..8fb380b 100644 Binary files a/temp/qrcodes/invoice_qrcode.png and b/temp/qrcodes/invoice_qrcode.png differ