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