diff --git a/.gitignore b/.gitignore index 1920042..819ae17 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,87 @@ test-qr-code.png patient_P002_folder_qr.png BSK-Client.exe BSK-Server.exe +image/checkup_media/11/IMG_11_20250807_003753_010.jpg +image/checkup_media/11/ultrasound_11_20250807_003847_781.jpg +image/checkup_media/11/ultrasound_11_20250807_003858_890.jpg +image/checkup_media/11/ultrasound_11_20250807_003906_933.jpg +image/checkup_media/11/ultrasound_11_20250807_003914_997.jpg +image/checkup_media/11/ultrasound_11_20250807_003915_084.jpg +image/checkup_media/11/ultrasound_11_20250807_003923_212.jpg +image/checkup_media/11/ultrasound_11_20250807_003923_242.jpg +image/checkup_media/11/ultrasound_11_20250807_003923_264.jpg +image/checkup_media/11/ultrasound_11_20250807_004608_225.jpg +image/checkup_media/11/ultrasound_11_20250807_004819_530.jpg +image/checkup_media/11/ultrasound_11_20250807_004819_620.jpg +image/checkup_media/11/ultrasound_11_20250807_013046_645.jpg +image/checkup_media/11/ultrasound_11_20250807_013107_151.jpg +image/checkup_media/11/ultrasound_11_20250807_013112_326.jpg +image/checkup_media/11/ultrasound_11_20250807_013208_111.jpg +image/checkup_media/14/ultrasound_14_20250806_174214_972.jpg +image/checkup_media/14/ultrasound_14_20250806_174510_971.jpg +image/checkup_media/14/ultrasound_14_20250806_195036_269.jpg +image/checkup_media/14/ultrasound_14_20250806_195036_269.png +image/checkup_media/14/ultrasound_14_20250806_195153_854.jpg +image/checkup_media/15/ultrasound_15_20250806_173652_645.jpg +image/checkup_media/15/ultrasound_15_20250806_175028_824.png +image/checkup_media/15/ultrasound_15_20250806_180021_719.png +image/checkup_media/15/ultrasound_15_20250806_193225_786.png +image/checkup_media/17/ultrasound_17_20250806_190846_084.png +image/checkup_media/18/ultrasound_18_20250806_201110_018.jpg +image/checkup_media/18/ultrasound_18_20250806_201110_018.png +image/checkup_media/19/ultrasound_19_20250806_193443_309.png +image/checkup_media/21/IMG_21_20250806_194138.jpg +image/checkup_media/21/IMG_21_20250806_194146.jpg +image/checkup_media/21/ultrasound_21_20250806_193903_008.png +image/checkup_media/21/ultrasound_21_20250806_200943_018.jpg +image/checkup_media/21/ultrasound_21_20250806_200951_145.jpg +image/checkup_media/21/ultrasound_21_20250806_200956_217.jpg +image/checkup_media/22/IMG_22_20250806_201405.jpg +image/checkup_media/22/IMG_22_20250806_201406.jpg +image/checkup_media/22/ultrasound_22_20250806_201421_818.jpg +image/checkup_media/22/ultrasound_22_20250806_202012_092.jpg +image/checkup_media/23/IMG_23_20250806_194657.jpg +image/checkup_media/23/ultrasound_23_20250806_194603_710.png +image/checkup_media/23/ultrasound_23_20250806_200659_652.jpg +image/checkup_media/23/ultrasound_23_20250806_200713_833.jpg +image/checkup_media/23/ultrasound_23_20250806_200713_833.png +image/checkup_media/23/ultrasound_23_20250806_200722_021.jpg +image/checkup_media/23/ultrasound_23_20250806_200722_021.png +image/checkup_media/23/ultrasound_23_20250806_200727_120.jpg +image/checkup_media/23/ultrasound_23_20250806_200727_120.png +image/checkup_media/23/ultrasound_23_20250807_000218_556.jpg +image/checkup_media/23/ultrasound_23_20250807_001116_025.jpg +image/checkup_media/23/ultrasound_23_20250807_001116_040.jpg +image/checkup_media/23/ultrasound_23_20250807_001237_342.jpg +image/checkup_media/24/IMG_24_20250806_235843.jpg +image/checkup_media/24/IMG_24_20250806_235844.jpg +image/checkup_media/24/IMG_24_20250806_235850.jpg +image/checkup_media/24/IMG_24_20250806_235851.jpg +image/checkup_media/24/IMG_24_20250806_235856.jpg +image/checkup_media/24/IMG_24_20250806_235909.jpg +image/checkup_media/24/IMG_24_20250806_235912.jpg +image/checkup_media/24/IMG_24_20250807_000144.jpg +image/checkup_media/24/IMG_24_20250807_000145.jpg +image/checkup_media/25/IMG_25_20250807_013413_631.jpg +image/checkup_media/25/ultrasound_25_20250807_004559_019.jpg +image/checkup_media/25/ultrasound_25_20250807_004603_120.jpg +image/checkup_media/25/ultrasound_25_20250807_004728_046.jpg +image/checkup_media/25/ultrasound_25_20250807_004839_727.jpg +image/checkup_media/25/ultrasound_25_20250807_004846_911.jpg +image/checkup_media/25/ultrasound_25_20250807_012733_392.jpg +image/checkup_media/25/ultrasound_25_20250807_012837_496.jpg +image/checkup_media/25/ultrasound_25_20250807_012847_663.jpg +image/checkup_media/26/IMG_26_20250807_014535_293.jpg +image/checkup_media/26/IMG_26_20250807_014536_259.jpg +image/checkup_media/27/IMG_27_20250807_015905_367.jpg +image/checkup_media/27/IMG_27_20250807_015908_874.jpg +image/checkup_media/27/IMG_27_20250807_015915_538.jpg +image/checkup_media/27/ultrasound_27_20250807_015955_956.jpg +image/checkup_media/27/ultrasound_27_20250807_020013_235.jpg +image/checkup_media/27/ultrasound_27_20250807_020016_272.jpg +image/checkup_media/28/IMG_28_20250807_023922_126.jpg +image/checkup_media/28/ultrasound_28_20250807_023901_955.jpg +image/checkup_media/8/IMG_8_20250807_001010.jpg +image/checkup_media/8/IMG_8_20250807_001050.jpg +image/checkup_media/8/ultrasound_8_20250807_001232_940.jpg +image/checkup_media/8/ultrasound_8_20250807_001253_394.jpg diff --git a/.vscode/launch.json b/.vscode/launch.json index 12cb1f6..623ee7d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,20 @@ { "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "DebugFileWalk", + "request": "launch", + "mainClass": "BsK.client.ui.component.CheckUpPage.DebugFileWalk", + "projectName": "ldS_BsK" + }, + { + "type": "java", + "name": "DebugFileWalk", + "request": "launch", + "mainClass": "DebugFileWalk", + "projectName": "ldS_BsK" + }, { "type": "java", "name": "TextUtils", diff --git a/ANH SIEU AM/New folder (2)/eed78d50-3b49-4dd9-ab8e-12212e7481fd.jpg b/ANH SIEU AM/New folder (2)/eed78d50-3b49-4dd9-ab8e-12212e7481fd.jpg new file mode 100644 index 0000000..79253f8 Binary files /dev/null and b/ANH SIEU AM/New folder (2)/eed78d50-3b49-4dd9-ab8e-12212e7481fd.jpg differ diff --git a/DebugFileWalk.java b/DebugFileWalk.java new file mode 100644 index 0000000..0f2a805 --- /dev/null +++ b/DebugFileWalk.java @@ -0,0 +1,49 @@ +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class DebugFileWalk { + + public static void main(String[] args) { + // ▼▼▼ IMPORTANT ▼▼▼ + // Change this path to the EXACT folder you are trying to scan. + // Use double backslashes "\\" for Windows paths. + // Example: "C:\\Users\\YourName\\Pictures\\MyEvent" + Path mediaPath = Paths.get("ANH SIEU AM"); + // ▲▲▲ IMPORTANT ▲▲▲ + + + System.out.println("--- Starting Scan ---"); + System.out.println("Scanning Path: " + mediaPath.toAbsolutePath()); + System.out.println("Does path exist? " + Files.exists(mediaPath)); + System.out.println("--------------------------------\n"); + + System.out.println(">>> ALL files and folders found by Files.walk():"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream.forEach(path -> { + System.out.println(" -> Found: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the scan:"); + e.printStackTrace(); + } + + System.out.println("\n--------------------------------"); + System.out.println(">>> Files that match the '.jpg' filter:"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream + .filter(path -> !Files.isDirectory(path)) + .filter(path -> path.toString().toLowerCase().endsWith(".jpg")) + .forEach(path -> { + System.out.println(" -> Matched: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the filtered scan:"); + e.printStackTrace(); + } + + System.out.println("\n--- Scan Finished ---"); + } +} \ No newline at end of file diff --git a/database/BSK.db b/database/BSK.db index c8e4bcd..d2e8d16 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 1d896bc..ecf8dc7 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 c644184..9a9f7fa 100644 Binary files a/database/BSK.db-wal and b/database/BSK.db-wal differ diff --git a/src/main/DebugFileWalk.java b/src/main/DebugFileWalk.java new file mode 100644 index 0000000..b0624f1 --- /dev/null +++ b/src/main/DebugFileWalk.java @@ -0,0 +1,51 @@ +package BsK.client.ui.component.CheckUpPage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class DebugFileWalk { + + public static void main(String[] args) { + // ▼▼▼ IMPORTANT ▼▼▼ + // Change this path to the EXACT folder you are trying to scan. + // Use double backslashes "\\" for Windows paths. + // Example: "C:\\Users\\YourName\\Pictures\\MyEvent" + Path mediaPath = Paths.get("ANH SIEU AM"); + // ▲▲▲ IMPORTANT ▲▲▲ + + + System.out.println("--- Starting Scan ---"); + System.out.println("Scanning Path: " + mediaPath.toAbsolutePath()); + System.out.println("Does path exist? " + Files.exists(mediaPath)); + System.out.println("--------------------------------\n"); + + System.out.println(">>> ALL files and folders found by Files.walk():"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream.forEach(path -> { + System.out.println(" -> Found: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the scan:"); + e.printStackTrace(); + } + + System.out.println("\n--------------------------------"); + System.out.println(">>> Files that match the '.jpg' filter:"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream + .filter(path -> !Files.isDirectory(path)) + .filter(path -> path.toString().toLowerCase().endsWith(".jpg")) + .forEach(path -> { + System.out.println(" -> Matched: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the filtered scan:"); + e.printStackTrace(); + } + + System.out.println("\n--- Scan Finished ---"); + } +} \ No newline at end of file diff --git a/src/main/java/BsK/client/Client.java b/src/main/java/BsK/client/Client.java index a98fa62..aab996e 100644 --- a/src/main/java/BsK/client/Client.java +++ b/src/main/java/BsK/client/Client.java @@ -177,7 +177,7 @@ public class Client { protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new HttpClientCodec()) - .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) + .addLast(new HttpObjectAggregator(100 * 1024 * 1024)) .addLast( new WebSocketClientProtocolHandler( finalUri, @@ -185,7 +185,7 @@ public class Client { null, true, new DefaultHttpHeaders(), - 50 * 1024 * 1024)) + 100 * 1024 * 1024)) .addLast("ws", ClientHandler.INSTANCE); } }); 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 89ba94a..7c392c4 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -129,8 +129,23 @@ import BsK.common.packet.res.UploadCheckupPdfResponse; import BsK.common.util.date.DateUtils; import javax.swing.text.JTextComponent; +import java.nio.file.DirectoryStream; +import java.util.Collections; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; + + @Slf4j public class CheckUpPage extends JPanel { + + private ScheduledExecutorService folderScanExecutor; + private volatile boolean isScanning = false; + private static final int SCAN_INTERVAL_SECONDS = 2; + private static final int MAX_FOLDERS_TO_SCAN = 4; + private MainFrame mainFrame; private List patientQueue = new ArrayList<>(); private String[][] rawQueueForTv = new String[][]{}; @@ -329,7 +344,7 @@ public class CheckUpPage extends JPanel { queueManagementPage = new QueueManagementPage(); // Initialize ultrasound folder monitoring - initializeUltrasoundFolderWatcher(); + initializeUltrasoundFolderScanner(); // Stop automatic webcam discovery to prevent background scanning try { @@ -613,15 +628,18 @@ public class CheckUpPage extends JPanel { patientInfoInnerPanel.add(idLabel, gbcPatient); gbcPatient.gridx = 1; gbcPatient.weightx = 0.4; - customerIdField = new JTextField(15); - customerIdField.setFont(fieldFont); - customerIdField.setEditable(false); - patientInfoInnerPanel.add(customerIdField, gbcPatient); - - // Initialize checkupIdField checkupIdField = new JTextField(15); checkupIdField.setFont(fieldFont); checkupIdField.setEditable(false); + patientInfoInnerPanel.add(checkupIdField, gbcPatient); + + customerIdField = new JTextField(15); + customerIdField.setFont(fieldFont); + customerIdField.setEditable(false); + + + // Initialize checkupIdField + gbcPatient.gridx = 2; gbcPatient.weightx = 0.1; JLabel phoneLabel = new JLabel("SĐT", SwingConstants.RIGHT); @@ -1581,26 +1599,6 @@ public class CheckUpPage extends JPanel { ); } }); - - // Listen for queue updates - ClientHandler.addResponseListener(GetCheckUpQueueUpdateResponse.class, response -> { - SwingUtilities.invokeLater(() -> { - log.info("Received checkup update queue"); - this.rawQueueForTv = response.getQueue(); - this.patientQueue.clear(); - if (this.rawQueueForTv != null) { - for (String[] patientData : this.rawQueueForTv) { - this.patientQueue.add(new Patient(patientData)); - } - } - if (queueManagementPage != null) { - queueManagementPage.updateQueueTable(); - } - if (tvQueueFrame != null && tvQueueFrame.isVisible()) { - tvQueueFrame.updateQueueData(this.rawQueueForTv); - } - }); - }); } private void addSelectAllOnFocus(JTextComponent textComponent) { @@ -3011,129 +3009,192 @@ public class CheckUpPage extends JPanel { // === Ultrasound Folder Monitoring Methods === - private void initializeUltrasoundFolderWatcher() { + private void initializeUltrasoundFolderScanner() { try { // Create the ultrasound folder if it doesn't exist Path ultrasoundPath = Paths.get(ULTRASOUND_FOLDER_PATH); - log.info("Initializing ultrasound folder watcher for path: {}", ultrasoundPath.toAbsolutePath()); + log.info("Initializing ultrasound folder scanner for path: {}", ultrasoundPath.toAbsolutePath()); if (!Files.exists(ultrasoundPath)) { Files.createDirectories(ultrasoundPath); log.info("Created ultrasound folder: {}", ultrasoundPath.toAbsolutePath()); - } else { - log.info("Ultrasound folder already exists: {}", ultrasoundPath.toAbsolutePath()); } - // Initialize watch service - watchService = FileSystems.getDefault().newWatchService(); - ultrasoundPath.register(watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY); + // Start scanning in a scheduled thread + folderScanExecutor = Executors.newSingleThreadScheduledExecutor(); + isScanning = true; - // Start watching in a separate thread - folderWatchExecutor = Executors.newSingleThreadExecutor(); - isWatchingFolder = true; + folderScanExecutor.scheduleWithFixedDelay( + this::scanUltrasoundFolders, + 0, // Initial delay + SCAN_INTERVAL_SECONDS, + TimeUnit.SECONDS + ); - folderWatchExecutor.submit(this::watchUltrasoundFolder); - - log.info("Started watching ultrasound folder: {} (Absolute: {})", - ULTRASOUND_FOLDER_PATH, ultrasoundPath.toAbsolutePath()); - - // SAFETY: Clean up any existing orphaned images on startup - cleanupOrphanedUltrasoundImages(); + log.info("Started ultrasound folder scanner with {} second intervals", SCAN_INTERVAL_SECONDS); } catch (Exception e) { - log.error("Failed to initialize ultrasound folder watcher: {}", e.getMessage(), e); - // Error logged only - no dialog popup + log.error("Failed to initialize ultrasound folder scanner: {}", e.getMessage(), e); } } - private void watchUltrasoundFolder() { - log.info("Ultrasound folder watcher thread started, waiting for events..."); - while (isWatchingFolder) { + private void scanUltrasoundFolders() { + if (!isScanning) { + return; + } + + try { + Path ultrasoundPath = Paths.get(ULTRASOUND_FOLDER_PATH); + + if (!Files.exists(ultrasoundPath)) { + return; + } + + // LEVEL 0: Check for images directly in the root "ANH SIEU AM" folder + scanDirectoryForImages(ultrasoundPath); + + // Get all first-level directories and sort by creation time (newest first) + List firstLevelFolders = getFirstLevelFoldersSortedByDate(ultrasoundPath); + + // Take only the latest 10 folders + List foldersToScan = firstLevelFolders.stream() + .limit(MAX_FOLDERS_TO_SCAN) + .collect(Collectors.toList()); + + + // LEVEL 1: Check for images directly in each first-level folder + for (Path folder : foldersToScan) { + if (!isScanning) break; + scanDirectoryForImages(folder); + } + + // LEVEL 2+: Scan each folder deeply for images in subdirectories + for (Path folder : foldersToScan) { + if (!isScanning) break; + scanSubdirectoriesForImages(folder); + } + + } catch (Exception e) { + log.error("Error during ultrasound folder scan: {}", e.getMessage(), e); + } + } + + private List getFirstLevelFoldersSortedByDate(Path parentPath) { + List folders = new ArrayList<>(); + + try (DirectoryStream stream = Files.newDirectoryStream(parentPath, Files::isDirectory)) { + for (Path folder : stream) { + folders.add(folder); + } + } catch (Exception e) { + log.error("Error listing first-level folders in {}: {}", parentPath, e.getMessage(), e); + return Collections.emptyList(); + } + + // Sort by creation time (newest first) + folders.sort((path1, path2) -> { try { - // Synchronized check to prevent race condition - synchronized (this) { - if (!isWatchingFolder || watchService == null) { - log.info("Watch service stopped or null, exiting watcher thread"); - break; - } - } - - log.info("Waiting for file system events..."); - WatchKey key = watchService.take(); // Wait for events - - // Check again after potentially blocking call - if (!isWatchingFolder) { - log.info("Watcher stopped while waiting for events"); - break; - } - - log.info("File system event detected!"); - - for (WatchEvent event : key.pollEvents()) { - // Check if we should still be watching - if (!isWatchingFolder) { - log.info("Watcher stopped during event processing"); - return; - } - - WatchEvent.Kind kind = event.kind(); - log.info("Event type: {}", kind); - - if (kind == StandardWatchEventKinds.OVERFLOW) { - log.warn("Event overflow detected, skipping"); - continue; - } - - Path fileName = (Path) event.context(); - Path fullPath = Paths.get(ULTRASOUND_FOLDER_PATH).resolve(fileName); - log.info("File event detected: {} at {}", fileName, fullPath.toAbsolutePath()); - - // Check if it's an image file - if (isImageFile(fileName.toString())) { - log.info("Detected new IMAGE in ultrasound folder: {} (Full path: {})", fileName, fullPath.toAbsolutePath()); - - // Wait a moment for file to be completely written - SwingUtilities.invokeLater(() -> { - Timer delayTimer = new Timer(500, e -> { - handleUltrasoundImageDetected(fullPath); - }); - delayTimer.setRepeats(false); - delayTimer.start(); - }); - } else { - log.info("File is not an image: {}", fileName); - } - } - - boolean valid = key.reset(); - if (!valid) { - log.warn("Watch key is no longer valid, stopping watcher"); - break; - } - - } catch (InterruptedException e) { - log.info("Ultrasound folder watcher interrupted"); - Thread.currentThread().interrupt(); - break; - } catch (java.nio.file.ClosedWatchServiceException e) { - log.info("Watch service was closed, stopping watcher thread"); - break; + FileTime time1 = Files.readAttributes(path1, BasicFileAttributes.class).creationTime(); + FileTime time2 = Files.readAttributes(path2, BasicFileAttributes.class).creationTime(); + return time2.compareTo(time1); // Newest first } catch (Exception e) { - log.error("Error in ultrasound folder watcher: {}", e.getMessage(), e); - // Don't break on general exceptions, just log and continue - try { - Thread.sleep(1000); // Wait a bit before retrying - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - break; + log.warn("Error comparing creation times for {} and {}: {}", path1, path2, e.getMessage()); + return 0; + } + }); + + return folders; + } + + private void scanFolderForImages(Path folder) { + try { + scanFolderRecursively(folder); + } catch (Exception e) { + log.error("Error scanning folder {}: {}", folder, e.getMessage(), e); + } + } + + // Scan a single directory for images (non-recursive) + private void scanDirectoryForImages(Path directory) { + if (!Files.exists(directory) || !Files.isDirectory(directory) || !isScanning) { + return; + } + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path entry : stream) { + if (!isScanning) break; + + // Only check files, not subdirectories + if (Files.isRegularFile(entry) && isImageFile(entry.getFileName().toString())) { + handleUltrasoundImageDetected(entry); } } + } catch (Exception e) { + log.error("Error scanning directory for images {}: {}", directory, e.getMessage(), e); } - log.info("Ultrasound folder watcher thread ended"); } + // Scan subdirectories recursively for images + private void scanSubdirectoriesForImages(Path directory) { + if (!Files.exists(directory) || !Files.isDirectory(directory) || !isScanning) { + return; + } + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path entry : stream) { + if (!isScanning) break; + + // Only process subdirectories, skip files (already handled in scanDirectoryForImages) + if (Files.isDirectory(entry)) { + scanFolderRecursively(entry); + } + } + } catch (Exception e) { + log.error("Error scanning subdirectories {}: {}", directory, e.getMessage(), e); + } + } + + private void scanFolderRecursively(Path directory) { + if (!Files.exists(directory) || !Files.isDirectory(directory) || !isScanning) { + return; + } + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path entry : stream) { + if (!isScanning) break; + + if (Files.isDirectory(entry)) { + // Recursively scan subdirectories + scanFolderRecursively(entry); + } else if (Files.isRegularFile(entry) && isImageFile(entry.getFileName().toString())) { + // Found an image file + handleUltrasoundImageDetected(entry); + } + } + } catch (Exception e) { + log.error("Error scanning directory {}: {}", directory, e.getMessage(), e); + } + } + + private void stopUltrasoundFolderScanner() { + log.info("Stopping ultrasound folder scanner..."); + isScanning = false; + + if (folderScanExecutor != null) { + folderScanExecutor.shutdown(); + try { + if (!folderScanExecutor.awaitTermination(5, TimeUnit.SECONDS)) { + folderScanExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + folderScanExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + log.info("Ultrasound folder scanner stopped"); + } + + // Keep your existing handleUltrasoundImageDetected method unchanged private void handleUltrasoundImageDetected(Path imagePath) { log.info("=== HANDLING ULTRASOUND IMAGE: {} ===", imagePath.toAbsolutePath()); log.info("Current patient ID: {}", currentCheckupIdForMedia); @@ -3142,20 +3203,16 @@ public class CheckUpPage extends JPanel { if (currentCheckupIdForMedia == null || currentCheckupIdForMedia.trim().isEmpty()) { log.warn("No patient selected, DELETING ultrasound image for safety: {}", imagePath); - // SAFETY: Delete the image immediately to prevent mismatched patient data try { Files.deleteIfExists(imagePath); log.info("Successfully deleted unassigned ultrasound image: {}", imagePath); } catch (Exception deleteEx) { log.error("Failed to delete unassigned ultrasound image: {}", deleteEx.getMessage(), deleteEx); } - - // Safety handled silently - logged only return; } try { - // SAFETY: Double-check patient ID hasn't changed during processing String safePatientId = currentCheckupIdForMedia; if (safePatientId == null || safePatientId.trim().isEmpty()) { log.error("SAFETY ERROR: Patient ID became null during ultrasound image processing! Deleting image."); @@ -3163,24 +3220,18 @@ public class CheckUpPage extends JPanel { return; } - // Wait for file to be completely written - Thread.sleep(1000); - if (!Files.exists(imagePath)) { log.warn("Ultrasound image file no longer exists: {}", imagePath); return; } - // SAFETY: Final check that patient ID is still valid if (!safePatientId.equals(currentCheckupIdForMedia)) { log.error("SAFETY ERROR: Patient ID changed during processing! Expected: {}, Current: {}. Deleting image for safety.", safePatientId, currentCheckupIdForMedia); Files.deleteIfExists(imagePath); - // Safety handled silently - logged only return; } - // Generate unique filename for the patient's folder String originalFileName = imagePath.getFileName().toString(); String fileExtension = ""; int lastDotIndex = originalFileName.lastIndexOf('.'); @@ -3191,13 +3242,11 @@ public class CheckUpPage extends JPanel { String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS").format(new Date()); String newFileName = "ultrasound_" + safePatientId + "_" + timestamp + fileExtension; - // Move to patient's media folder Path targetPath = currentCheckupMediaPath.resolve(newFileName); Files.move(imagePath, targetPath); log.info("Moved ultrasound image from {} to {}", imagePath, targetPath); - // Upload the image to server (same as chụp ảnh function) try { BufferedImage image = ImageIO.read(targetPath.toFile()); if (image != null) { @@ -3211,21 +3260,18 @@ public class CheckUpPage extends JPanel { } SwingUtilities.invokeLater(() -> { - // Refresh the image gallery to show the new image if (currentCheckupMediaPath != null && Files.exists(currentCheckupMediaPath)) { loadAndDisplayImages(currentCheckupMediaPath); } - - // Success logged only - no dialog popup log.info("✅ SUCCESS: Ultrasound image processed for patient {} - File: {}", safePatientId, newFileName); }); } catch (Exception e) { log.error("Error handling ultrasound image: {}", e.getMessage(), e); - // Error logged only - no dialog popup } } - + + private boolean isImageFile(String fileName) { if (fileName == null) return false; String lowercaseName = fileName.toLowerCase(); @@ -3491,7 +3537,7 @@ public class CheckUpPage extends JPanel { return; } - String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS").format(new Date()); String fileName = "IMG_" + currentCheckupIdForMedia + "_" + timestamp + ".jpg"; Path filePath = currentCheckupMediaPath.resolve(fileName); @@ -3521,65 +3567,36 @@ public class CheckUpPage extends JPanel { } } - private void uploadImageInBackground(String fileName, BufferedImage image) { - imageUploadExecutor.submit(() -> { - try { - // Compress the image to JPEG with high quality (0.9) for better preservation - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - // Get JPEG writer and set high quality compression - ImageWriter jpegWriter = ImageIO.getImageWritersByFormatName("jpg").next(); - ImageOutputStream ios = ImageIO.createImageOutputStream(baos); - jpegWriter.setOutput(ios); - - // Set compression quality to 0.9 (90% quality) - ImageWriteParam writeParam = jpegWriter.getDefaultWriteParam(); - writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - writeParam.setCompressionQuality(0.9f); // High quality compression - - // Write the image with high quality - jpegWriter.write(null, new IIOImage(image, null, null), writeParam); - - // Clean up - jpegWriter.dispose(); - ios.close(); - - byte[] compressedImageData = baos.toByteArray(); + + +private void uploadImageInBackground(String fileName, BufferedImage image) { + imageUploadExecutor.submit(() -> { + try { + // Convert BufferedImage to byte array without compression + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write image as PNG to preserve original quality + ImageIO.write(image, "png", baos); + + byte[] imageData = baos.toByteArray(); + baos.close(); - // Create and send the request - String checkupId = currentCheckupIdForMedia; + // Create and send the request + String checkupId = currentCheckupIdForMedia; - // Keep original filename since we're already using JPG locally - String jpgFileName = fileName; + // Use PNG extension since we're preserving quality + String pngFileName = fileName.replaceAll("\\.(jpg|jpeg)$", ".png"); - // Schedule a timeout task for this upload - Future timeoutTask = timeoutExecutor.schedule(() -> { - // If the task is still in the map, it means we timed out - if (uploadTimeoutTasks.remove(jpgFileName) != null) { - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(CheckUpPage.this, - "Không nhận được phản hồi từ máy chủ khi tải lên ảnh: " + jpgFileName + "\n" + - "Vui lòng kiểm tra lại kết nối mạng (Wi-Fi/Internet).", - "Lỗi Mạng", - JOptionPane.WARNING_MESSAGE); - }); - } - }, 15, TimeUnit.SECONDS); // 15-second timeout - - // Store the task so we can cancel it if we get a response - uploadTimeoutTasks.put(jpgFileName, timeoutTask); - - UploadCheckupImageRequest request = new UploadCheckupImageRequest(checkupId, compressedImageData, jpgFileName); - NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); - log.info("Sent UploadCheckupImageRequest for {}", jpgFileName); - - } catch (IOException e) { - log.error("Failed to compress image for upload: {}", fileName, e); - // The timeout mechanism will handle notifying the user of a potential network issue. - } - }); - } + UploadCheckupImageRequest request = new UploadCheckupImageRequest(checkupId, imageData, pngFileName); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + log.info("Sent UploadCheckupImageRequest for {}", pngFileName); + } catch (IOException e) { + log.error("Failed to convert image for upload: {}", fileName, e); + // The timeout mechanism will handle notifying the user of a potential network issue. + } + }); +} private void handleUploadImageResponse(UploadCheckupImageResponse response) { String fileName = response.getFileName(); @@ -4434,9 +4451,13 @@ public class CheckUpPage extends JPanel { if (previouslySelectedId != null) { int rowToSelect = findRowByCheckupId(previouslySelectedId); + if (rowToSelect != -1) { - queueTable.setRowSelectionInterval(rowToSelect, rowToSelect); - queueTable.scrollRectToVisible(queueTable.getCellRect(rowToSelect, 0, true)); + int viewRow = queueTable.convertRowIndexToView(rowToSelect); + if (viewRow != -1) { + queueTable.setRowSelectionInterval(viewRow, viewRow); + queueTable.scrollRectToVisible(queueTable.getCellRect(viewRow, 0, true)); + } } else { JOptionPane.showMessageDialog( mainFrame, diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/DebugFileWalk.java b/src/main/java/BsK/client/ui/component/CheckUpPage/DebugFileWalk.java new file mode 100644 index 0000000..fc3db93 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/DebugFileWalk.java @@ -0,0 +1,54 @@ +package BsK.client.ui.component.CheckUpPage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class DebugFileWalk { + + public static void main(String[] args) { + // ▼▼▼ IMPORTANT ▼▼▼ + // Change this path to the EXACT folder you are trying to scan. + // Use double backslashes "\\" for Windows paths. + // Example: "C:\\Users\\YourName\\Pictures\\MyEvent" + Path mediaPath = Paths.get("ANH SIEU AM"); + // ▲▲▲ IMPORTANT ▲▲▲ + + + System.out.println("--- Starting Scan ---"); + System.out.println("Scanning Path: " + mediaPath.toAbsolutePath()); + System.out.println("Does path exist? " + Files.exists(mediaPath)); + System.out.println("--------------------------------\n"); + + System.out.println(">>> ALL files and folders found by Files.walk():"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream.forEach(path -> { + System.out.println(" -> Found: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the scan:"); + e.printStackTrace(); + } + + System.out.println("\n--------------------------------"); + System.out.println(">>> Files that match the '.jpg' filter:"); + try (Stream pathStream = Files.walk(mediaPath)) { + pathStream + .filter(path -> !Files.isDirectory(path)) + .filter(path -> { + String lowerName = path.toString().toLowerCase(); + return lowerName.endsWith(".jpg") || lowerName.endsWith(".jpeg") || lowerName.endsWith(".png"); + }) + .forEach(path -> { + System.out.println(" -> Matched: " + path); + }); + } catch (IOException e) { + System.err.println("An ERROR occurred during the filtered scan:"); + e.printStackTrace(); + } + + System.out.println("\n--- Scan Finished ---"); + } +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java b/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java index 39eae89..cb29204 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java @@ -6,26 +6,27 @@ import BsK.client.network.handler.ResponseListener; import BsK.client.ui.component.common.AddDialog.AddDialog; import BsK.common.entity.DoctorItem; import BsK.common.entity.Patient; +import BsK.common.packet.req.DeleteCheckupRequest; import BsK.common.packet.req.GetCheckupDataRequest; +import BsK.common.packet.res.DeleteCheckupResponse; import BsK.common.packet.res.GetCheckupDataResponse; import BsK.common.util.network.NetworkUtil; import BsK.client.ui.component.CheckUpPage.CheckUpPage; import javax.swing.*; import javax.swing.border.EmptyBorder; +import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.util.Calendar; -import java.util.Date; -import BsK.client.ui.component.DataDialog.ExcelExporter; -import javax.swing.filechooser.FileNameExtensionFilter; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; public class DataDialog extends JDialog { @@ -40,6 +41,7 @@ public class DataDialog extends JDialog { private DefaultTableModel tableModel; private JLabel resultCountLabel; private final ResponseListener dataResponseListener = this::handleGetCheckupDataResponse; + private final ResponseListener deleteResponseListener = this::handleDeleteCheckupResponse; // --- NEW --- private int currentPage = 1; private int totalPages = 1; private int recordsPerPage = 20; @@ -105,12 +107,14 @@ public class DataDialog extends JDialog { private void setupNetworking() { ClientHandler.addResponseListener(GetCheckupDataResponse.class, dataResponseListener); + ClientHandler.addResponseListener(DeleteCheckupResponse.class, deleteResponseListener); // --- NEW --- this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { // Clean up all listeners ClientHandler.deleteListener(GetCheckupDataResponse.class, dataResponseListener); + ClientHandler.deleteListener(DeleteCheckupResponse.class, deleteResponseListener); // --- NEW --- if (medicinePanel != null) { medicinePanel.cleanup(); } @@ -125,7 +129,7 @@ public class DataDialog extends JDialog { }); } - // + // private void loadFiltersFromLocalStorage() { if (LocalStorage.dataDialogSearchTerm == null || LocalStorage.dataDialogSearchTerm.isEmpty()) { searchField.setText("Tìm theo tên bệnh nhân, mã bệnh nhân..."); @@ -191,7 +195,7 @@ public class DataDialog extends JDialog { JButton clearFilterButton = new JButton("Xóa bộ lọc"); clearFilterButton.setPreferredSize(new Dimension(100, 30)); - + JButton getAllButton = new JButton("Lấy tất cả"); getAllButton.setPreferredSize(new Dimension(100, 30)); @@ -230,9 +234,20 @@ public class DataDialog extends JDialog { bottomRow.add(Box.createHorizontalStrut(20)); bottomRow.add(new JLabel("Bác sĩ:")); - bottomRow.add(Box.createHorizontalStrut(15)); // Thêm khoảng cách + doctorComboBox = new JComboBox<>(); + doctorComboBox.addItem("Tất cả"); + if (LocalStorage.doctorsName != null) { + for (DoctorItem doctor : LocalStorage.doctorsName) { + doctorComboBox.addItem(doctor.getName()); + } + } + doctorComboBox.setSelectedIndex(0); + doctorComboBox.setPreferredSize(new Dimension(150, 25)); + bottomRow.add(doctorComboBox); + + bottomRow.add(Box.createHorizontalStrut(15)); bottomRow.add(new JLabel("Mã Phiếu:")); - idSearchField = new JTextField("Tìm theo mã phiếu khám...", 12); // Tạo field mới + idSearchField = new JTextField("Tìm theo mã phiếu khám...", 12); idSearchField.setForeground(Color.GRAY); idSearchField.setPreferredSize(new Dimension(150, 25)); idSearchField.addFocusListener(new java.awt.event.FocusAdapter() { @@ -251,17 +266,6 @@ public class DataDialog extends JDialog { }); bottomRow.add(idSearchField); - doctorComboBox = new JComboBox<>(); - doctorComboBox.addItem("Tất cả"); - if (LocalStorage.doctorsName != null) { - for (DoctorItem doctor : LocalStorage.doctorsName) { - doctorComboBox.addItem(doctor.getName()); - } - } - doctorComboBox.setSelectedIndex(0); - doctorComboBox.setPreferredSize(new Dimension(150, 25)); - bottomRow.add(doctorComboBox); - controlPanel.add(topRow, BorderLayout.NORTH); controlPanel.add(bottomRow, BorderLayout.SOUTH); @@ -279,7 +283,7 @@ public class DataDialog extends JDialog { clearFilters(); fetchData(1); }); - + getAllButton.addActionListener(e -> { GetCheckupDataRequest request = new GetCheckupDataRequest(null, null,null, null, null, 1, recordsPerPage); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); @@ -300,7 +304,7 @@ public class DataDialog extends JDialog { tableModel = new DefaultTableModel(columnNames, 0) { @Override public boolean isCellEditable(int row, int column) { - return column == 9; + return column == 9; // Only the "Hành động" column is editable } }; dataTable = new JTable(tableModel); @@ -311,7 +315,7 @@ public class DataDialog extends JDialog { dataTable.setSelectionBackground(new Color(230, 240, 255)); TableColumn column; - int[] columnWidths = {40, 80, 150, 80, 60, 100, 130, 150, 200, 120}; + int[] columnWidths = {40, 80, 150, 80, 60, 100, 130, 150, 150, 120}; // Adjusted widths for (int i = 0; i < columnWidths.length; i++) { column = dataTable.getColumnModel().getColumn(i); column.setPreferredWidth(columnWidths[i]); @@ -336,9 +340,8 @@ public class DataDialog extends JDialog { checkupIdSearch = null; } - // Ưu tiên tìm theo Mã phiếu khám. Nếu có, bỏ qua tìm kiếm chung. if (checkupIdSearch != null) { - searchTerm = null; + searchTerm = null; searchField.setText("Tìm theo tên bệnh nhân, mã bệnh nhân..."); searchField.setForeground(Color.GRAY); } @@ -393,7 +396,6 @@ public class DataDialog extends JDialog { } try { - // <<< MODIFIED: Pass the doctors list to the exporter ExcelExporter.exportToExcel(patientsToExport, LocalStorage.doctorsName, this.fileForExport); JOptionPane.showMessageDialog(this, "Xuất file Excel thành công!\n" + this.fileForExport.getAbsolutePath(), "Thành công", JOptionPane.INFORMATION_MESSAGE); } catch (IOException ex) { @@ -410,7 +412,7 @@ public class DataDialog extends JDialog { this.totalPages = response.getTotalPages(); this.totalRecords = response.getTotalRecords(); this.recordsPerPage = response.getPageSize(); - this.currentCheckupData = response.getCheckupData(); + this.currentCheckupData = response.getCheckupData(); tableModel.setRowCount(0); if (this.currentCheckupData != null) { @@ -420,7 +422,7 @@ public class DataDialog extends JDialog { Patient patient = new Patient(rowData); String[] tableRow = { String.valueOf(stt++), - patient.getCustomerId(), + patient.getCheckupId(), // Changed from getCustomerId() to getCheckupId() patient.getCustomerLastName() + " " + patient.getCustomerFirstName(), patient.getCustomerDob(), patient.getCustomerGender(), @@ -428,7 +430,7 @@ public class DataDialog extends JDialog { patient.getDoctorName(), patient.getDiagnosis(), patient.getConclusion(), - "" + "" // Placeholder for action buttons }; tableModel.addRow(tableRow); } catch (IllegalArgumentException e) { @@ -441,6 +443,23 @@ public class DataDialog extends JDialog { updatePaginationControls(); }); } + + /** + * --- NEW --- + * Handles the response from the server after a delete request. + * @param response The response packet from the server. + */ + private void handleDeleteCheckupResponse(DeleteCheckupResponse response) { + SwingUtilities.invokeLater(() -> { + if (response.isSuccess()) { + JOptionPane.showMessageDialog(this, "Xóa phiếu khám thành công!", "Thành công", JOptionPane.INFORMATION_MESSAGE); + // Refresh the table to show the change. + fetchData(currentPage); + } else { + JOptionPane.showMessageDialog(this, "Lỗi khi xóa phiếu khám: " + response.getMessage(), "Lỗi", JOptionPane.ERROR_MESSAGE); + } + }); + } private void updateResultCountLabel() { if (totalRecords == 0) { @@ -465,10 +484,8 @@ public class DataDialog extends JDialog { } this.isExporting = true; - JOptionPane.showMessageDialog(this, "Đang chuẩn bị dữ liệu để xuất file...", "Thông báo", JOptionPane.INFORMATION_MESSAGE); - - fetchData(-1); + fetchData(-1); // Fetch all data for export } } @@ -525,17 +542,10 @@ public class DataDialog extends JDialog { private void saveFiltersToLocalStorage() { String searchTerm = searchField.getText(); - if (searchTerm.equals("Tìm theo tên bệnh nhân, mã bệnh nhân...")) { - LocalStorage.dataDialogSearchTerm = ""; - } else { - LocalStorage.dataDialogSearchTerm = searchTerm; - } + LocalStorage.dataDialogSearchTerm = searchTerm.equals("Tìm theo tên bệnh nhân, mã bệnh nhân...") ? "" : searchTerm; + String idSearchTerm = idSearchField.getText(); - if (idSearchTerm.equals("Tìm theo mã phiếu khám...")) { - LocalStorage.dataDialogIdSearchTerm = ""; - } else { - LocalStorage.dataDialogIdSearchTerm = idSearchTerm; - } + LocalStorage.dataDialogIdSearchTerm = idSearchTerm.equals("Tìm theo mã phiếu khám...") ? "" : idSearchTerm; LocalStorage.dataDialogFromDate = (Date) fromDateSpinner.getValue(); LocalStorage.dataDialogToDate = (Date) toDateSpinner.getValue(); @@ -545,6 +555,8 @@ public class DataDialog extends JDialog { private void clearFilters() { searchField.setText("Tìm theo tên bệnh nhân, mã bệnh nhân..."); searchField.setForeground(Color.GRAY); + idSearchField.setText("Tìm theo mã phiếu khám..."); + idSearchField.setForeground(Color.GRAY); doctorComboBox.setSelectedIndex(0); Date today = new Date(); @@ -552,88 +564,131 @@ public class DataDialog extends JDialog { toDateSpinner.setValue(today); LocalStorage.dataDialogSearchTerm = ""; + LocalStorage.dataDialogIdSearchTerm = ""; LocalStorage.dataDialogFromDate = today; LocalStorage.dataDialogToDate = today; LocalStorage.dataDialogDoctorName = "Tất cả"; - LocalStorage.dataDialogIdSearchTerm = ""; } + // + + // + + /** + * --- MODIFIED --- + * Renders both an Edit and a Delete button in the "Hành động" column. + */ class ActionButtonRenderer extends JPanel implements TableCellRenderer { - private JButton editButton; + private final JButton editButton; + private final JButton deleteButton; public ActionButtonRenderer() { - setLayout(new FlowLayout(FlowLayout.CENTER, 2, 2)); + setLayout(new FlowLayout(FlowLayout.CENTER, 5, 2)); + // Edit Button ImageIcon editIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/edit.png"); - if (editIcon.getImage() != null) { - editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); - } + editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); editButton = new JButton(editIcon); - editButton.setPreferredSize(new Dimension(30, 25)); - editButton.setToolTipText("Sửa"); + // Delete Button + ImageIcon deleteIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/delete.png"); + deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); + deleteButton = new JButton(deleteIcon); + deleteButton.setPreferredSize(new Dimension(30, 25)); + deleteButton.setToolTipText("Xóa"); + add(editButton); + add(deleteButton); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (isSelected) { - setBackground(table.getSelectionBackground()); - } else { - setBackground(table.getBackground()); - } + setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); return this; } } + /** + * --- MODIFIED --- + * Cell editor for the "Hành động" column, handling clicks for both Edit and Delete buttons. + */ class ActionButtonEditor extends DefaultCellEditor { - private JPanel panel; - private JButton editButton; + private final JPanel panel; + private final JButton editButton; + private final JButton deleteButton; public ActionButtonEditor(JDialog parentDialog) { super(new JCheckBox()); - panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 2, 2)); + panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 2)); + // Edit Button Setup ImageIcon editIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/edit.png"); - if (editIcon.getImage() != null) { - editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); - } + editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); editButton = new JButton(editIcon); - editButton.setPreferredSize(new Dimension(30, 25)); + editButton.setToolTipText("Sửa"); + // Delete Button Setup + ImageIcon deleteIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/delete.png"); + deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)); + deleteButton = new JButton(deleteIcon); + deleteButton.setPreferredSize(new Dimension(30, 25)); + deleteButton.setToolTipText("Xóa"); + + // Action Listener for Edit Button editButton.addActionListener(e -> { fireEditingStopped(); - int selectedViewRow = dataTable.getSelectedRow(); - if (selectedViewRow < 0) { - return; - } + if (selectedViewRow < 0) return; int modelRow = dataTable.convertRowIndexToModel(selectedViewRow); if (currentCheckupData != null && modelRow >= 0 && modelRow < currentCheckupData.length) { String[] rowData = currentCheckupData[modelRow]; try { Patient selectedPatient = new Patient(rowData); - if (checkUpPageInstance != null) { checkUpPageInstance.loadPatientByCheckupId(selectedPatient); } - parentDialog.dispose(); - } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(parentDialog, "Không thể tải dữ liệu bệnh nhân. Dữ liệu không hợp lệ.", "Lỗi", JOptionPane.ERROR_MESSAGE); - System.err.println("Error creating Patient DTO from row data: " + ex.getMessage()); } } }); + // Action Listener for Delete Button + deleteButton.addActionListener(e -> { + fireEditingStopped(); + int selectedViewRow = dataTable.getSelectedRow(); + if (selectedViewRow < 0) return; + int modelRow = dataTable.convertRowIndexToModel(selectedViewRow); + String checkupIdStr = (String) tableModel.getValueAt(modelRow, 1); + String patientName = (String) tableModel.getValueAt(modelRow, 2); + + int confirmation = JOptionPane.showConfirmDialog( + parentDialog, + "Bạn có chắc chắn muốn xóa phiếu khám của bệnh nhân:\n" + patientName + " (Mã khám: " + checkupIdStr + ")?\n\nHành động này sẽ xóa toàn bộ dữ liệu liên quan và không thể hoàn tác.", + "Xác nhận xóa", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ); + + if (confirmation == JOptionPane.YES_OPTION) { + try { + long checkupId = Long.parseLong(checkupIdStr); + DeleteCheckupRequest deleteRequest = new DeleteCheckupRequest(checkupId); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), deleteRequest); + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog(parentDialog, "Mã khám không hợp lệ: " + checkupIdStr, "Lỗi", JOptionPane.ERROR_MESSAGE); + } + } + }); panel.add(editButton); + panel.add(deleteButton); } @Override @@ -647,4 +702,4 @@ public class DataDialog extends JDialog { } } // -} \ No newline at end of file +} 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 685e90f..bb5257c 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 @@ -99,7 +99,6 @@ public class AddDialog extends JDialog { } else { JOptionPane.showMessageDialog(this, response.getError()); } - setVisible(false); } private void sendGetRecentPatientRequest() { diff --git a/src/main/java/BsK/common/packet/req/DeleteCheckupRequest.java b/src/main/java/BsK/common/packet/req/DeleteCheckupRequest.java new file mode 100644 index 0000000..c618ee5 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/DeleteCheckupRequest.java @@ -0,0 +1,14 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.Data; + +@Data +public class DeleteCheckupRequest implements Packet { + private long checkupId; + + public DeleteCheckupRequest(long checkupId) { + this.checkupId = checkupId; + } + +} diff --git a/src/main/java/BsK/common/packet/res/DeleteCheckupResponse.java b/src/main/java/BsK/common/packet/res/DeleteCheckupResponse.java new file mode 100644 index 0000000..befee38 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/DeleteCheckupResponse.java @@ -0,0 +1,12 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; +@Data +@AllArgsConstructor +public class DeleteCheckupResponse implements Packet{ + private boolean success; + private String message; + +} diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index e020e20..2dec306 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -291,7 +291,7 @@ public class Server { ch.pipeline() .addLast(new HttpServerCodec()) .addLast(new ChunkedWriteHandler()) - .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) + .addLast(new HttpObjectAggregator(100 * 1024 * 1024)) .addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024)) .addLast(new ServerHandler()); } diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index 721ee39..13fdb9f 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -8,6 +8,7 @@ import BsK.common.packet.res.*; import BsK.common.Error; import BsK.common.entity.Status; import BsK.common.util.date.DateUtils; +import BsK.common.util.network.NetworkUtil; import BsK.server.Server; import BsK.server.ServerDashboard; import BsK.server.network.manager.SessionManager; @@ -877,6 +878,7 @@ public class ServerHandler extends SimpleChannelInboundHandler