diff --git a/src/main/java/BsK/client/LocalStorage.java b/src/main/java/BsK/client/LocalStorage.java index 9dffe98..96e0b46 100644 --- a/src/main/java/BsK/client/LocalStorage.java +++ b/src/main/java/BsK/client/LocalStorage.java @@ -1,5 +1,6 @@ package BsK.client; +import BsK.common.entity.DoctorItem; import BsK.server.network.entity.Role; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,7 +9,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import BsK.client.ui.component.CheckUpPage.DoctorItem; @AllArgsConstructor @Data diff --git a/src/main/java/BsK/client/network/handler/ClientHandler.java b/src/main/java/BsK/client/network/handler/ClientHandler.java index f88e32d..29051af 100644 --- a/src/main/java/BsK/client/network/handler/ClientHandler.java +++ b/src/main/java/BsK/client/network/handler/ClientHandler.java @@ -3,6 +3,7 @@ package BsK.client.network.handler; import BsK.client.LocalStorage; import BsK.client.ui.handler.UIHandler; +import BsK.common.entity.DoctorItem; import BsK.common.packet.Packet; import BsK.common.packet.PacketSerializer; import BsK.common.packet.req.GetDoctorGeneralInfo; @@ -27,7 +28,6 @@ import lombok.extern.slf4j.Slf4j; import org.w3c.dom.Text; import BsK.client.ui.component.CheckUpPage.AddDialog.AddDialog; import BsK.client.ui.component.CheckUpPage.CheckUpPage; -import BsK.client.ui.component.CheckUpPage.DoctorItem; import BsK.client.ui.component.LoginPage.LoginPage; import BsK.client.ui.component.MainFrame; import BsK.common.packet.res.GetDoctorGeneralInfoResponse; diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/add.png b/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/add.png deleted file mode 100644 index 3697b35..0000000 Binary files a/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/add.png and /dev/null differ 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 6231715..7f1df53 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -4,18 +4,19 @@ import BsK.client.Client; import BsK.client.LocalStorage; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; -import BsK.client.ui.component.CheckUpPage.AddDialog.AddDialog; -import BsK.client.ui.component.CheckUpPage.HistoryViewDialog.HistoryViewDialog; -import BsK.client.ui.component.CheckUpPage.MedicineDialog.MedicineDialog; -import BsK.client.ui.component.CheckUpPage.PrintDialog.MedicineInvoice; -import BsK.client.ui.component.CheckUpPage.PrintDialog.UltrasoundResult; -import BsK.client.ui.component.CheckUpPage.ServiceDialog.ServiceDialog; -import BsK.client.ui.component.CheckUpPage.TemplateDialog.TemplateDialog; +import BsK.client.ui.component.common.AddDialog.AddDialog; +import BsK.client.ui.component.common.HistoryViewDialog.HistoryViewDialog; +import BsK.client.ui.component.common.MedicineDialog.MedicineDialog; +import BsK.client.ui.component.common.PrintDialog.MedicineInvoice; +import BsK.client.ui.component.common.PrintDialog.UltrasoundResult; +import BsK.client.ui.component.common.ServiceDialog.ServiceDialog; +import BsK.client.ui.component.common.TemplateDialog.TemplateDialog; import BsK.client.ui.component.MainFrame; import BsK.client.ui.component.common.DateLabelFormatter; import BsK.client.ui.component.common.NavBar; import BsK.client.ui.component.common.RoundedPanel; import BsK.client.ui.component.QueueViewPage.QueueViewPage; +import BsK.common.entity.DoctorItem; import BsK.common.entity.Patient; import BsK.common.entity.Status; import BsK.common.entity.Template; @@ -153,7 +154,6 @@ import BsK.common.util.date.DateUtils; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import javax.swing.text.JTextComponent; -import BsK.client.ui.component.CheckUpPage.DoctorItem; @Slf4j public class CheckUpPage extends JPanel { @@ -172,6 +172,7 @@ public class CheckUpPage extends JPanel { private final ResponseListener getAllTemplatesListener = this::handleGetAllTemplatesResponse; private final ResponseListener uploadImageResponseListener = this::handleUploadImageResponse; private final ResponseListener uploadPdfResponseListener = this::handleUploadPdfResponse; + private final ResponseListener syncImagesResponseListener = this::handleSyncImagesResponse; private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField, customerCccdDdcnField; private JTextArea suggestionField, diagnosisField, conclusionField; // Changed symptomsField to suggestionField private JTextPane notesField; @@ -353,6 +354,7 @@ public class CheckUpPage extends JPanel { ClientHandler.addResponseListener(GetAllTemplatesRes.class, getAllTemplatesListener); ClientHandler.addResponseListener(UploadCheckupImageResponse.class, uploadImageResponseListener); ClientHandler.addResponseListener(UploadCheckupPdfResponse.class, uploadPdfResponseListener); + ClientHandler.addResponseListener(SyncCheckupImagesResponse.class, syncImagesResponseListener); // Instantiate the new queue page but don't show it yet queueManagementPage = new QueueManagementPage(); @@ -2289,6 +2291,8 @@ public class CheckUpPage extends JPanel { NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetPatientHistoryRequest(Integer.parseInt(selectedPatient.getCustomerId()))); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetOrderInfoByCheckupReq(selectedPatient.getCheckupId())); + // Sync images from server to client for consistency + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new SyncCheckupImagesRequest(selectedPatient.getCheckupId())); String fullAddress = selectedPatient.getCustomerAddress(); String[] addressParts = fullAddress.split(", "); @@ -3442,20 +3446,20 @@ public class CheckUpPage extends JPanel { } String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - String fileName = "IMG_" + currentCheckupIdForMedia + "_" + timestamp + ".png"; + String fileName = "IMG_" + currentCheckupIdForMedia + "_" + timestamp + ".jpg"; Path filePath = currentCheckupMediaPath.resolve(fileName); try { BufferedImage image = selectedWebcam.getImage(); if (image != null) { - // Save locally first for immediate UI feedback (PNG for high quality local cache) - ImageIO.write(image, "PNG", filePath.toFile()); - log.info("Picture taken and saved locally at: {}", filePath); + // Save locally as JPG for consistency (compressed format for client storage) + ImageIO.write(image, "JPG", filePath.toFile()); + log.info("Picture taken and saved locally as JPG at: {}", filePath); // Refresh UI to show the new image immediately loadAndDisplayImages(currentCheckupMediaPath); - // Now, upload in the background (will be compressed to JPG) + // Now, upload in the background (server will convert to PNG for archival) uploadImageInBackground(filePath.getFileName().toString(), image); } else { @@ -3499,8 +3503,8 @@ public class CheckUpPage extends JPanel { // Create and send the request String checkupId = currentCheckupIdForMedia; - // Adjust file name to have .jpg extension for the server - String jpgFileName = fileName.substring(0, fileName.lastIndexOf('.')) + ".jpg"; + // Keep original filename since we're already using JPG locally + String jpgFileName = fileName; // Schedule a timeout task for this upload Future timeoutTask = timeoutExecutor.schedule(() -> { @@ -3568,6 +3572,84 @@ public class CheckUpPage extends JPanel { } } + private void handleSyncImagesResponse(SyncCheckupImagesResponse response) { + if (response.isSuccess()) { + log.info("Successfully synced images for checkup {}: {}", response.getCheckupId(), response.getMessage()); + + SwingUtilities.invokeLater(() -> { + try { + // Create media directory for this checkup if it doesn't exist + java.io.File mediaDir = new java.io.File("src/main/resources/image/checkup_media", response.getCheckupId()); + if (!mediaDir.exists()) { + mediaDir.mkdirs(); + } + + // Save each image to local media directory, converting to JPG format for client consistency + for (int i = 0; i < response.getImageNames().size(); i++) { + String imageName = response.getImageNames().get(i); + byte[] imageData = response.getImageDatas().get(i); + + // Convert filename to JPG extension for client storage consistency + String jpgImageName = imageName.substring(0, imageName.lastIndexOf('.')) + ".jpg"; + java.io.File imageFile = new java.io.File(mediaDir, jpgImageName); + + try { + // Convert image data to JPG format for client storage + try (java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageData)) { + java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(bais); + if (image != null) { + javax.imageio.ImageIO.write(image, "JPG", imageFile); + log.debug("Synced and converted image to JPG: {}", imageFile.getAbsolutePath()); + } else { + // Fallback: save as original format if conversion fails + try (java.io.FileOutputStream fos = new java.io.FileOutputStream(new java.io.File(mediaDir, imageName))) { + fos.write(imageData); + log.warn("Failed to convert image to JPG, saved as original format: {}", imageName); + } + } + } + } catch (Exception e) { + log.error("Failed to process synced image {}: {}", imageName, e.getMessage()); + // Fallback: save as original format + try (java.io.FileOutputStream fos = new java.io.FileOutputStream(new java.io.File(mediaDir, imageName))) { + fos.write(imageData); + log.warn("Saved image as original format due to conversion error: {}", imageName); + } + } + } + + // Update the current media path and refresh the gallery if this is for the current checkup + if (response.getCheckupId().equals(currentCheckupIdForMedia)) { + currentCheckupMediaPath = mediaDir.toPath(); + loadAndDisplayImages(currentCheckupMediaPath); + log.info("Refreshed image gallery with {} synced images", response.getImageNames().size()); + } + + // Update UI status + if (callingStatusLabel != null) { + callingStatusLabel.setText("Đã đồng bộ " + response.getImageNames().size() + " hình ảnh từ server"); + callingStatusLabel.setForeground(new Color(0, 100, 0)); + } + + } catch (Exception e) { + log.error("Failed to save synced images locally: {}", e.getMessage()); + if (callingStatusLabel != null) { + callingStatusLabel.setText("Lỗi khi lưu hình ảnh đồng bộ: " + e.getMessage()); + callingStatusLabel.setForeground(Color.RED); + } + } + }); + } else { + log.warn("Image sync failed for checkup {}: {}", response.getCheckupId(), response.getMessage()); + SwingUtilities.invokeLater(() -> { + if (callingStatusLabel != null) { + callingStatusLabel.setText("Đồng bộ hình ảnh: " + response.getMessage()); + callingStatusLabel.setForeground(new Color(200, 100, 0)); // Orange for warning + } + }); + } + } + /** * Uploads PDF to backend asynchronously * @param ultrasoundResult The UltrasoundResult object to export as PDF diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/Debug.java b/src/main/java/BsK/client/ui/component/CheckUpPage/Debug.java deleted file mode 100644 index 0e1490c..0000000 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/Debug.java +++ /dev/null @@ -1,49 +0,0 @@ -package BsK.client.ui.component.CheckUpPage; - -import BsK.client.LocalStorage; -import BsK.client.network.handler.ClientHandler; -import BsK.common.packet.res.GetWardResponse; - -import lombok.extern.slf4j.Slf4j; - -/** - * Debug class to help troubleshoot ward loading issues - * This is just a test class and should be removed after fixing the issue - */ -@Slf4j -public class Debug { - - /** - * Log ward data when received to help diagnose any issues - */ - public static void logWardInfo() { - log.info("DEBUG: Setting up ward monitoring"); - - ClientHandler.addResponseListener(GetWardResponse.class, (response) -> { - log.info("DEBUG: Received ward response with {} wards", - (response.getWards() != null ? response.getWards().length - 1 : 0)); - - if (response.getWards() == null || response.getWards().length <= 1) { - log.warn("DEBUG: Ward data is empty!"); - return; - } - - log.info("DEBUG: First 5 wards:"); - for (int i = 0; i < Math.min(5, response.getWards().length); i++) { - log.info("DEBUG: Ward[{}] = '{}'", i, response.getWards()[i]); - } - - log.info("DEBUG: First 5 ward IDs:"); - int count = 0; - for (String ward : response.getWardToId().keySet()) { - if (count >= 5) break; - log.info("DEBUG: Ward '{}' -> ID '{}'", ward, response.getWardToId().get(ward)); - count++; - } - - // Check LocalStorage after processing - log.info("DEBUG: LocalStorage.wards now contains {} elements", - (LocalStorage.wards != null ? LocalStorage.wards.length : 0)); - }); - } -} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/Demo.java b/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/Demo.java deleted file mode 100644 index fc6006a..0000000 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/Demo.java +++ /dev/null @@ -1,34 +0,0 @@ -package BsK.client.ui.component.CheckUpPage.MedicineDialog; - -import javax.swing.*; -import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator; -import java.awt.*; -public class Demo { - - JFrame frame = new JFrame(""); - AutoCompleteDecorator decorator; - - - public Demo() { - //JTable demo - JFrame frame = new JFrame(); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(300, 200); - JTable table = new JTable(new String[][] { - { "1", "2", "3" }, - { "4", "5", "6" }, - { "7", "8", "9" }, - { "10", "11", "12" } - }, new String[] { "A", "B", "C" }); - //set size of custom colum - table.getColumnModel().getColumn(0).setPreferredWidth(200); - JScrollPane scrollPane = new JScrollPane(table); - frame.add(scrollPane, BorderLayout.NORTH); - - frame.setVisible(true); - } - - public static void main(String[] args) { - Demo d = new Demo(); - } -} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/DataPage/DataPage.java b/src/main/java/BsK/client/ui/component/DataPage/DataPage.java new file mode 100644 index 0000000..59306f2 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/DataPage/DataPage.java @@ -0,0 +1,4813 @@ +package BsK.client.ui.component.DataPage; + +import BsK.client.Client; +import BsK.client.LocalStorage; +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.client.ui.component.common.AddDialog.AddDialog; +import BsK.client.ui.component.common.HistoryViewDialog.HistoryViewDialog; +import BsK.client.ui.component.common.MedicineDialog.MedicineDialog; +import BsK.client.ui.component.common.PrintDialog.MedicineInvoice; +import BsK.client.ui.component.common.PrintDialog.UltrasoundResult; +import BsK.client.ui.component.common.ServiceDialog.ServiceDialog; +import BsK.client.ui.component.common.TemplateDialog.TemplateDialog; +import BsK.client.ui.component.MainFrame; +import BsK.client.ui.component.common.DateLabelFormatter; +import BsK.client.ui.component.common.NavBar; +import BsK.client.ui.component.common.RoundedPanel; +import BsK.client.ui.component.QueueViewPage.QueueViewPage; +import BsK.common.entity.DoctorItem; +import BsK.common.entity.Patient; +import BsK.common.entity.Status; +import BsK.common.entity.Template; +import BsK.common.packet.req.*; +import BsK.common.packet.res.*; +import BsK.common.util.network.NetworkUtil; +import BsK.common.util.text.TextUtils; +import lombok.extern.slf4j.Slf4j; +import org.jdatepicker.impl.JDatePanelImpl; +import org.jdatepicker.impl.JDatePickerImpl; +import org.jdatepicker.impl.UtilDateModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableModel; +import javax.swing.RowSorter; +import javax.swing.SortOrder; +import javax.swing.table.TableRowSorter; +import java.util.Comparator; +import java.text.SimpleDateFormat; +import java.text.ParseException; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.ImageWriteParam; +import javax.imageio.IIOImage; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.datatransfer.*; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.awt.image.BufferedImage; + +// Imports for new functionality +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.WatchService; +import java.nio.file.WatchKey; +import java.nio.file.WatchEvent; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.FileSystems; +import javax.imageio.ImageIO; +import javax.swing.JColorChooser; + +// Webcam imports +import com.github.sarxos.webcam.Webcam; +import com.github.sarxos.webcam.WebcamDiscoveryEvent; +import com.github.sarxos.webcam.WebcamDiscoveryListener; +import com.github.sarxos.webcam.WebcamEvent; +import com.github.sarxos.webcam.WebcamListener; +import com.github.sarxos.webcam.WebcamPanel; +import com.github.sarxos.webcam.WebcamResolution; + +// JavaCV imports for video recording +import org.bytedeco.javacv.FFmpegFrameRecorder; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.Java2DFrameConverter; +import org.bytedeco.javacv.OpenCVFrameConverter; +import org.bytedeco.opencv.opencv_core.IplImage; +import static org.bytedeco.opencv.global.opencv_core.cvFlip; + +// Java imports for concurrent tasks +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import javax.swing.JFrame; +import javax.swing.JTextPane; +import javax.swing.JToolBar; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.rtf.RTFEditorKit; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.io.InputStream; +import java.io.StringReader; + +// --- JasperReports Imports --- +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.JasperPrintManager; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.view.JasperViewer; +// --- End JasperReports Imports --- + +// Add new imports +import BsK.common.entity.PatientHistory; +import BsK.common.packet.req.GetPatientHistoryRequest; +import BsK.common.packet.res.GetPatientHistoryResponse; +import BsK.common.packet.req.SaveCheckupRequest; +import BsK.common.packet.req.SearchAllRole; +import BsK.common.packet.req.SearchAllUser; +import BsK.common.packet.req.UploadCheckupImageRequest; +import BsK.common.packet.req.UploadCheckupPdfRequest; +import BsK.common.packet.res.AddCheckupResponse; +import BsK.common.packet.res.AddPatientResponse; +import BsK.common.packet.res.RegisterSuccessResponse; +import BsK.common.packet.res.SaveCheckupRes; +import BsK.common.packet.res.UploadCheckupImageResponse; +import BsK.common.packet.res.UploadCheckupPdfResponse; +import BsK.common.util.date.DateUtils; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import javax.swing.text.JTextComponent; + +@Slf4j +public class DataPage extends JPanel { + private MainFrame mainFrame; + private List patientQueue = new ArrayList<>(); + private String[][] rawQueueForTv = new String[][]{}; + private String[][] history; + private DefaultTableModel historyModel; + private JTable historyTable; + private final ResponseListener checkUpQueueUpdateListener = this::handleGetCheckUpQueueUpdateResponse; + private final ResponseListener checkUpQueueListener = this::handleGetCheckUpQueueResponse; + private final ResponseListener patientHistoryListener = this::handleGetPatientHistoryResponse; + private final ResponseListener wardResponseListener = this::handleGetWardResponse; + private final ResponseListener callPatientResponseListener = this::handleCallPatientResponse; + private final ResponseListener orderInfoByCheckupListener = this::handleGetOrderInfoByCheckupResponse; + private final ResponseListener getAllTemplatesListener = this::handleGetAllTemplatesResponse; + private final ResponseListener uploadImageResponseListener = this::handleUploadImageResponse; + private final ResponseListener uploadPdfResponseListener = this::handleUploadPdfResponse; + private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField, customerCccdDdcnField; + private JTextArea suggestionField, diagnosisField, conclusionField; // Changed symptomsField to suggestionField + private JTextPane notesField; + private JComboBox doctorComboBox, ultrasoundDoctorComboBox; + private JComboBox statusComboBox, genderComboBox, provinceComboBox, wardComboBox, checkupTypeComboBox, templateComboBox, orientationComboBox; // Added orientationComboBox + private JCheckBox needRecheckupCheckbox; // Checkbox to indicate if re-checkup is needed + private JSpinner customerWeightSpinner, customerHeightSpinner, patientHeartRateSpinner, bloodPressureSystolicSpinner, bloodPressureDiastolicSpinner; + private JDatePickerImpl datePicker, dobPicker, recheckupDatePicker; + private JButton recheckupDatePickerButton; + private String[][] medicinePrescription = new String[0][0]; // Initialize to empty + private String[][] servicePrescription = new String[0][0]; // Initialize to empty + private MedicineDialog medDialog = null; + private ServiceDialog serDialog = null; + private AddDialog addDialog = null; + private String selectedCheckupId = null; // Use checkupId to track selection instead of row index + private boolean saved = true; // Initially true, changed when patient selected or dialog opened. + private DefaultComboBoxModel wardModel; + + // Variables to store target ward and ward when loading patient address + private String targetWard = null; + private JComboBox callRoomComboBox; + private JPanel rightContainer; // Add this field + private List