From 75aa04d77f2163974c683dcc4081ffa91c22674a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Fri, 4 Jul 2025 00:34:46 +0700 Subject: [PATCH] [update] Enhance Client and Server functionality by increasing HTTP object aggregator limits for improved data handling. Refactor CheckUpPage and HistoryViewDialog to support additional patient history details, including diagnosis and conclusion, and implement image retrieval for checkups, enhancing user experience and data management. --- src/main/java/BsK/client/Client.java | 4 +- .../client/network/handler/ClientHandler.java | 15 +- .../ui/component/CheckUpPage/CheckUpPage.java | 175 ++++++++---- .../HistoryViewDialog/HistoryViewDialog.java | 262 ++++++++++-------- .../packet/req/GetImagesByCheckupIdReq.java | 11 + .../packet/res/GetImagesByCheckupIdRes.java | 14 + src/main/java/BsK/server/Server.java | 4 +- .../server/network/handler/ServerHandler.java | 60 +++- src/main/resources/database/BSK.db | Bin 1175552 -> 1179648 bytes 9 files changed, 356 insertions(+), 189 deletions(-) create mode 100644 src/main/java/BsK/common/packet/req/GetImagesByCheckupIdReq.java create mode 100644 src/main/java/BsK/common/packet/res/GetImagesByCheckupIdRes.java diff --git a/src/main/java/BsK/client/Client.java b/src/main/java/BsK/client/Client.java index 598dd0b..7994e24 100644 --- a/src/main/java/BsK/client/Client.java +++ b/src/main/java/BsK/client/Client.java @@ -142,7 +142,7 @@ public class Client { .addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS)) .addLast(new HttpClientCodec()) - .addLast(new HttpObjectAggregator(8192)) + .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) .addLast( new WebSocketClientProtocolHandler( finalUri, @@ -150,7 +150,7 @@ public class Client { null, true, new DefaultHttpHeaders(), - 100000)) + 50 * 1024 * 1024)) .addLast("ws", ClientHandler.INSTANCE); } }); diff --git a/src/main/java/BsK/client/network/handler/ClientHandler.java b/src/main/java/BsK/client/network/handler/ClientHandler.java index 7a8f4f3..c72e57b 100644 --- a/src/main/java/BsK/client/network/handler/ClientHandler.java +++ b/src/main/java/BsK/client/network/handler/ClientHandler.java @@ -60,16 +60,12 @@ public class ClientHandler extends SimpleChannelInboundHandler { - log.debug("Key: {}", key.getName()); - - }); - - // Process other packets + // Dispatch to registered listeners List> responseListeners = listeners.get(packet.getClass()); if (responseListeners != null && !responseListeners.isEmpty()) { for (ResponseListener listener : new ArrayList<>(responseListeners)) { 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 9cc1cd8..3a951e3 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -1100,18 +1100,24 @@ public class CheckUpPage extends JPanel { historyTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1) { + if (e.getClickCount() == 1) { // Single click is enough int selectedRow = historyTable.getSelectedRow(); - if (selectedRow >= 0 && selectedRow < history.length) { - // Get selected history data + // Check if the row index is valid for our data array + if (selectedRow >= 0 && history != null && selectedRow < history.length) { String[] selectedHistory = history[selectedRow]; - if (selectedHistory.length > 1) { - String checkupId = selectedHistory[1]; // Corrected: Index 1 is the checkup ID + // Ensure the selected record has enough data fields + if (selectedHistory != null && selectedHistory.length >= 8) { + String checkupId = selectedHistory[1]; String patientName = customerFirstNameField.getText() + " " + customerLastNameField.getText(); - String checkupDate = selectedHistory[0]; // Corrected: Index 0 is the date + String checkupDate = selectedHistory[0]; + String suggestion = selectedHistory[2]; + String diagnosis = selectedHistory[3]; + String notes = selectedHistory[5]; + String conclusion = selectedHistory[7]; - // Open history view dialog - openHistoryViewDialog(checkupId, patientName, checkupDate); + openHistoryViewDialog(checkupId, patientName, checkupDate, suggestion, diagnosis, conclusion, notes); + } else { + log.warn("Selected history record has incomplete data. Length: {}", (selectedHistory != null ? selectedHistory.length : "null")); } } } @@ -1703,15 +1709,16 @@ public class CheckUpPage extends JPanel { String[][] tableData = new String[this.history.length][5]; for (int i = 0; i < this.history.length; i++) { if (this.history[i] != null && this.history[i].length >= 6) { - tableData[i][0] = this.history[i][0]; // "Ngày Tháng" (Date) - tableData[i][1] = this.history[i][1]; // "Mã khám bệnh" (ID) - tableData[i][2] = this.history[i][2]; // "Triệu chứng" (Suggestion) - tableData[i][3] = this.history[i][3]; // "Chẩn đoán" (Diagnosis) - tableData[i][4] = this.history[i][5]; // "Ghi chú" (Notes) - was incorrectly showing prescription_id + // 0: checkup_date, 1: checkup_id, 2: suggestion, 3: diagnosis, 4: prescription_id, 5: notes, 6: checkup_type, 7: conclusion + // display 0, 1, 6, 7 + tableData[i][0] = this.history[i][0]; + tableData[i][1] = this.history[i][1]; + tableData[i][2] = this.history[i][6]; + tableData[i][3] = this.history[i][7]; } } - historyModel.setDataVector(tableData, new String[]{"Ngày Tháng","Mã khám bệnh", "Triệu chứng", "Chẩn đoán", "Ghi chú"}); + historyModel.setDataVector(tableData, new String[]{"Ngày Tháng","Mã khám bệnh", "Loại khám", "Kết luận"}); }); } @@ -2579,6 +2586,15 @@ public class CheckUpPage extends JPanel { JLayeredPane layeredPane = new JLayeredPane(); layeredPane.setPreferredSize(new Dimension(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)); layeredPane.setBorder(BorderFactory.createLineBorder(Color.GRAY)); + layeredPane.setCursor(new Cursor(Cursor.HAND_CURSOR)); // Set the hand cursor + + // Add the click listener directly to the image label + imageLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + showFullImageDialog(imageFile); + } + }); // The label will fill the entire pane imageLabel.setBounds(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); @@ -2622,6 +2638,50 @@ public class CheckUpPage extends JPanel { imageGalleryPanel.repaint(); } + private void showFullImageDialog(File imageFile) { + try { + BufferedImage fullImage = ImageIO.read(imageFile); + if (fullImage == null) { + JOptionPane.showMessageDialog(this, "Không thể tải hình ảnh.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + + // Create a dialog to show the image + JDialog imageDialog = new JDialog(mainFrame, "Xem ảnh - " + imageFile.getName(), true); + imageDialog.setLayout(new BorderLayout()); + + // Scale image to fit screen dimensions + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int maxWidth = (int) (screenSize.width * 0.9); + int maxHeight = (int) (screenSize.height * 0.9); + + int imgWidth = fullImage.getWidth(); + int imgHeight = fullImage.getHeight(); + + double scale = Math.min(1.0, Math.min((double) maxWidth / imgWidth, (double) maxHeight / imgHeight)); + + int scaledWidth = (int) (imgWidth * scale); + int scaledHeight = (int) (imgHeight * scale); + + Image scaledImage = fullImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH); + JLabel imageLabel = new JLabel(new ImageIcon(scaledImage)); + + // Add label to a scroll pane in case it's still large + JScrollPane scrollPane = new JScrollPane(imageLabel); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + imageDialog.add(scrollPane, BorderLayout.CENTER); + + // Set dialog size and position + imageDialog.setSize(scaledWidth + 20, scaledHeight + 45); // Add padding for borders and title bar + imageDialog.setLocationRelativeTo(this); + imageDialog.setVisible(true); + + } catch (IOException e) { + log.error("Error opening full image view for {}", imageFile.getAbsolutePath(), e); + JOptionPane.showMessageDialog(this, "Lỗi khi mở ảnh: " + e.getMessage(), "Lỗi", JOptionPane.ERROR_MESSAGE); + } + } + private void ensureMediaDirectoryExists(String checkupId) { try { // First ensure base directory exists @@ -3044,9 +3104,10 @@ public class CheckUpPage extends JPanel { /** * Opens the history view dialog for the selected checkup */ - private void openHistoryViewDialog(String checkupId, String patientName, String checkupDate) { + private void openHistoryViewDialog(String checkupId, String patientName, String checkupDate, String suggestion, String diagnosis, String conclusion, String notes) { try { - HistoryViewDialog historyDialog = new HistoryViewDialog(mainFrame, checkupId, patientName, checkupDate); + // Pass all the necessary details to the dialog + HistoryViewDialog historyDialog = new HistoryViewDialog(mainFrame, checkupId, patientName, checkupDate, suggestion, diagnosis, conclusion, notes); historyDialog.setVisible(true); } catch (Exception e) { log.error("Error opening history view dialog", e); @@ -3058,55 +3119,57 @@ public class CheckUpPage extends JPanel { } private void clearPatientDetails() { - // Reset all fields to default values - checkupIdField.setText(""); - customerLastNameField.setText(""); - customerFirstNameField.setText(""); - customerAddressField.setText(""); - customerPhoneField.setText(""); - customerIdField.setText(""); - suggestionField.setText(""); - diagnosisField.setText(""); - conclusionField.setText(""); - setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); // Clear notes + if (SwingUtilities.isEventDispatchThread()) { + // Reset all fields to default values + checkupIdField.setText(""); + customerLastNameField.setText(""); + customerFirstNameField.setText(""); + customerAddressField.setText(""); + customerPhoneField.setText(""); + customerIdField.setText(""); + suggestionField.setText(""); + diagnosisField.setText(""); + conclusionField.setText(""); + setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); // Clear notes - if (doctorComboBox.getItemCount() > 0) doctorComboBox.setSelectedIndex(0); - statusComboBox.setSelectedIndex(0); - genderComboBox.setSelectedIndex(0); - if (provinceComboBox.getItemCount() > 0) provinceComboBox.setSelectedIndex(0); - // district and ward will be cleared by province selection listener - checkupTypeComboBox.setSelectedIndex(0); - if (templateComboBox.getItemCount() > 0) templateComboBox.setSelectedIndex(0); + if (doctorComboBox.getItemCount() > 0) doctorComboBox.setSelectedIndex(0); + statusComboBox.setSelectedIndex(0); + genderComboBox.setSelectedIndex(0); + if (provinceComboBox.getItemCount() > 0) provinceComboBox.setSelectedIndex(0); + // district and ward will be cleared by province selection listener + checkupTypeComboBox.setSelectedIndex(0); + if (templateComboBox.getItemCount() > 0) templateComboBox.setSelectedIndex(0); - customerWeightSpinner.setValue(0); - customerHeightSpinner.setValue(0); + customerWeightSpinner.setValue(0); + customerHeightSpinner.setValue(0); - datePicker.getModel().setValue(null); - dobPicker.getModel().setValue(null); + datePicker.getModel().setValue(null); + dobPicker.getModel().setValue(null); - medicinePrescription = new String[0][0]; - servicePrescription = new String[0][0]; - updatePrescriptionTree(); + medicinePrescription = new String[0][0]; + servicePrescription = new String[0][0]; + updatePrescriptionTree(); - historyModel.setRowCount(0); // Clear history table + historyModel.setRowCount(0); // Clear history table - // Disable all action buttons - for (Component comp : ((JPanel)((JPanel)rightContainer.getComponent(1))).getComponents()) { - if (comp instanceof JButton) { - comp.setEnabled(false); + // Disable all action buttons + for (Component comp : ((JPanel)((JPanel)rightContainer.getComponent(1))).getComponents()) { + if (comp instanceof JButton) { + comp.setEnabled(false); + } } - } - // Clear media view - if (imageGalleryPanel != null) { - imageGalleryPanel.removeAll(); - imageGalleryPanel.add(new JLabel("Chọn bệnh nhân để xem media.")); - imageGalleryPanel.revalidate(); - imageGalleryPanel.repaint(); - } + // Clear media view + if (imageGalleryPanel != null) { + imageGalleryPanel.removeAll(); + imageGalleryPanel.add(new JLabel("Chọn bệnh nhân để xem media.")); + imageGalleryPanel.revalidate(); + imageGalleryPanel.repaint(); + } - selectedCheckupId = null; - saved = true; // No patient loaded, so it's in a "saved" state. + selectedCheckupId = null; + saved = true; // No patient loaded, so it's in a "saved" state. + } } } diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/HistoryViewDialog/HistoryViewDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/HistoryViewDialog/HistoryViewDialog.java index f75e403..7ffb60a 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/HistoryViewDialog/HistoryViewDialog.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/HistoryViewDialog/HistoryViewDialog.java @@ -11,6 +11,7 @@ import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -22,10 +23,13 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.imageio.ImageIO; +import javax.swing.text.rtf.RTFEditorKit; import lombok.extern.slf4j.Slf4j; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; +import BsK.common.packet.req.GetImagesByCheckupIdReq; import BsK.common.packet.req.GetOrderInfoByCheckupReq; +import BsK.common.packet.res.GetImagesByCheckupIdRes; import BsK.common.packet.res.GetOrderInfoByCheckupRes; import BsK.common.util.network.NetworkUtil; @@ -38,6 +42,7 @@ public class HistoryViewDialog extends JDialog { private String checkupDate; private String content; private String suggestion; + private String diagnosis; private String conclusion; private String[][] medicinePrescription; private String[][] servicePrescription; @@ -46,8 +51,9 @@ public class HistoryViewDialog extends JDialog { private JLabel imagePreviewLabel; private JPanel imageListPanel; private JScrollPane imageListScrollPane; - private JTextArea contentArea; + private JTextPane contentArea; private JTextArea suggestionArea; + private JTextArea diagnosisArea; private JTextArea conclusionArea; private JTree prescriptionTree; private DefaultTreeModel prescriptionTreeModel; @@ -65,76 +71,67 @@ public class HistoryViewDialog extends JDialog { // Current selected image private BufferedImage currentSelectedImage; private List imageFiles; - private ResponseListener orderInfoListener; + private final ResponseListener orderInfoListener = this::handleGetOrderInfoResponse; + private final ResponseListener imagesListener = this::handleGetImagesResponse; - public HistoryViewDialog(Frame parent, String checkupId, String patientName, String checkupDate) { + public HistoryViewDialog(Frame parent, String checkupId, String patientName, String checkupDate, String suggestion, String diagnosis, String conclusion, String notes) { super(parent, "Xem chi tiết lịch sử khám - " + patientName, true); this.checkupId = checkupId; this.patientName = patientName; this.checkupDate = checkupDate; this.imageFiles = new ArrayList<>(); - // Initialize sample data for demonstration (will be overwritten by network response) - initializeSampleData(); + // Set data from parameters + this.content = notes; // This is the RTF content + this.suggestion = suggestion; + this.diagnosis = diagnosis; + this.conclusion = conclusion; initializeDialog(parent); setupUI(); - loadImagesForCheckup(); - updatePrescriptionTree(); + // Add listeners + ClientHandler.addResponseListener(GetOrderInfoByCheckupRes.class, orderInfoListener); + ClientHandler.addResponseListener(GetImagesByCheckupIdRes.class, imagesListener); + // Fetch real data from server fetchOrderInformation(); - } - - private void initializeSampleData() { - // Sample data - in real implementation, this would come from database - this.content = "Bệnh nhân có triệu chứng đau đầu, sốt nhẹ. Khám tổng quát không có bất thường đáng kể."; - this.suggestion = "Nghỉ ngơi đầy đủ, uống nhiều nước, theo dõi thêm 2-3 ngày."; - this.conclusion = "Cảm cúm thông thường, không có biến chứng."; - - // Sample medicine prescription - this.medicinePrescription = new String[][] { - {"1", "Paracetamol 500mg", "2", "viên", "1", "1", "1", "5000", "30000", "Uống sau ăn"}, - {"2", "Vitamin C", "1", "lọ", "0", "1", "0", "15000", "15000", "Uống trước ăn"} - }; - - // Sample service prescription - this.servicePrescription = new String[][] { - {"1", "Xét nghiệm máu", "1", "100000", "100000", "Xét nghiệm tổng quát"}, - {"2", "Siêu âm", "1", "200000", "200000", "Siêu âm bụng"} - }; + fetchCheckupImages(); } private void fetchOrderInformation() { try { - orderInfoListener = new ResponseListener<>() { - @Override - public void onResponse(GetOrderInfoByCheckupRes response) { - SwingUtilities.invokeLater(() -> { - try { - setMedicinePrescription(response.getMedicinePrescription()); - setServicePrescription(response.getServicePrescription()); - log.info("Successfully updated history order tree for checkupId: {}", checkupId); - } finally { - // Remove listener after execution to prevent memory leaks - if (orderInfoListener != null) { - ClientHandler.deleteListener(GetOrderInfoByCheckupRes.class, orderInfoListener); - orderInfoListener = null; - } - } - }); - } - }; - - ClientHandler.addResponseListener(GetOrderInfoByCheckupRes.class, orderInfoListener); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetOrderInfoByCheckupReq(this.checkupId)); log.info("Sent GetOrderInfoByCheckupReq for checkupId: {}", this.checkupId); } catch (Exception e) { log.error("Error sending GetOrderInfoByCheckupReq for checkupId: {}", this.checkupId, e); - if (orderInfoListener != null) { - ClientHandler.deleteListener(GetOrderInfoByCheckupRes.class, orderInfoListener); - orderInfoListener = null; - } + } + } + + private void fetchCheckupImages() { + try { + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetImagesByCheckupIdReq(this.checkupId)); + log.info("Sent GetImagesByCheckupIdReq for checkupId: {}", this.checkupId); + } catch (Exception e) { + log.error("Error sending GetImagesByCheckupIdReq for checkupId: {}", this.checkupId, e); + } + } + + private void handleGetOrderInfoResponse(GetOrderInfoByCheckupRes response) { + SwingUtilities.invokeLater(() -> { + setMedicinePrescription(response.getMedicinePrescription()); + setServicePrescription(response.getServicePrescription()); + log.info("Successfully updated history order tree for checkupId: {}", checkupId); + }); + } + + private void handleGetImagesResponse(GetImagesByCheckupIdRes response) { + log.info("Client received GetImagesByCheckupIdRes for checkupId: {}. Image count: {}", + response.getCheckupId(), response.getImageDatas() != null ? response.getImageDatas().size() : 0); + if (checkupId.equals(response.getCheckupId())) { + SwingUtilities.invokeLater(() -> { + updateImageList(response.getImageNames(), response.getImageDatas()); + }); } } @@ -162,10 +159,9 @@ public class HistoryViewDialog extends JDialog { @Override public void windowClosed(WindowEvent e) { - // Clean up listener when dialog is closed, just in case - if (orderInfoListener != null) { - ClientHandler.deleteListener(GetOrderInfoByCheckupRes.class, orderInfoListener); - } + // Clean up listeners when dialog is closed + ClientHandler.deleteListener(GetOrderInfoByCheckupRes.class, orderInfoListener); + ClientHandler.deleteListener(GetImagesByCheckupIdRes.class, imagesListener); } }); } @@ -376,18 +372,17 @@ public class HistoryViewDialog extends JDialog { tabbedPane.setFont(new Font("Arial", Font.BOLD, 12)); // Content tab - contentArea = new JTextArea(content); + contentArea = new JTextPane(); contentArea.setEditable(false); - contentArea.setLineWrap(true); - contentArea.setWrapStyleWord(true); contentArea.setFont(new Font("Arial", Font.PLAIN, 12)); contentArea.setBackground(new Color(248, 249, 250)); contentArea.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + setRtfContent(contentArea, this.content); JScrollPane contentScroll = new JScrollPane(contentArea); tabbedPane.addTab("Nội dung khám", contentScroll); // Suggestion tab - suggestionArea = new JTextArea(suggestion); + suggestionArea = new JTextArea(getDisplayableString(this.suggestion)); suggestionArea.setEditable(false); suggestionArea.setLineWrap(true); suggestionArea.setWrapStyleWord(true); @@ -397,8 +392,19 @@ public class HistoryViewDialog extends JDialog { JScrollPane suggestionScroll = new JScrollPane(suggestionArea); tabbedPane.addTab("Đề nghị", suggestionScroll); + // Diagnosis tab + diagnosisArea = new JTextArea(getDisplayableString(this.diagnosis)); + diagnosisArea.setEditable(false); + diagnosisArea.setLineWrap(true); + diagnosisArea.setWrapStyleWord(true); + diagnosisArea.setFont(new Font("Arial", Font.PLAIN, 12)); + diagnosisArea.setBackground(new Color(248, 249, 250)); + diagnosisArea.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + JScrollPane diagnosisScroll = new JScrollPane(diagnosisArea); + tabbedPane.addTab("Chẩn đoán", diagnosisScroll); + // Conclusion tab - conclusionArea = new JTextArea(conclusion); + conclusionArea = new JTextArea(getDisplayableString(this.conclusion)); conclusionArea.setEditable(false); conclusionArea.setLineWrap(true); conclusionArea.setWrapStyleWord(true); @@ -468,32 +474,42 @@ public class HistoryViewDialog extends JDialog { return panel; } - private void loadImagesForCheckup() { + private void updateImageList(List imageNames, List imageDatas) { SwingUtilities.invokeLater(() -> { - imageFiles.clear(); imageListPanel.removeAll(); - // Load images from checkup media directory - Path mediaPath = Paths.get(CHECKUP_MEDIA_BASE_DIR, checkupId); - - if (Files.exists(mediaPath) && Files.isDirectory(mediaPath)) { - try { - Files.list(mediaPath) - .filter(path -> isImageFile(path.toString())) - .forEach(path -> { - File imageFile = path.toFile(); - imageFiles.add(imageFile); - - // Create thumbnail button - JButton thumbnailButton = createThumbnailButton(imageFile); - imageListPanel.add(thumbnailButton); - }); - } catch (IOException e) { - log.error("Error loading images for checkup {}", checkupId, e); + if (imageNames != null && !imageNames.isEmpty()) { + log.info("Populating image list with {} images for checkupId: {}", imageNames.size(), checkupId); + for (int i = 0; i < imageNames.size(); i++) { + byte[] imageData = imageDatas.get(i); + String imageName = imageNames.get(i); + try { + ByteArrayInputStream bais = new ByteArrayInputStream(imageData); + BufferedImage image = ImageIO.read(bais); + if (image != null) { + JButton thumbnailButton = createThumbnailButton(image, imageName); + imageListPanel.add(thumbnailButton); + } else { + log.warn("Failed to decode image data for '{}'. ImageIO.read returned null. Data length: {} bytes.", imageName, imageData.length); + } + } catch (IOException e) { + log.error("Error decoding image data for {}", imageName, e); + } } - } - - if (imageFiles.isEmpty()) { + // Display the first image automatically + if (!imageDatas.isEmpty()) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(imageDatas.get(0)); + BufferedImage firstImage = ImageIO.read(bais); + if (firstImage != null) { + displayFullImage(firstImage, imageNames.get(0)); + } + } catch (IOException e) { + log.error("Error decoding first image", e); + } + } + } else { + log.info("No images found for checkupId: {}. Displaying 'No images' label.", checkupId); JLabel noImagesLabel = new JLabel("Không có hình ảnh nào"); noImagesLabel.setFont(new Font("Arial", Font.ITALIC, 12)); noImagesLabel.setForeground(Color.GRAY); @@ -505,26 +521,22 @@ public class HistoryViewDialog extends JDialog { }); } - private JButton createThumbnailButton(File imageFile) { + private JButton createThumbnailButton(BufferedImage image, String imageName) { JButton button = new JButton(); button.setPreferredSize(new Dimension(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)); button.setBorder(BorderFactory.createRaisedBevelBorder()); button.setFocusPainted(false); button.setCursor(new Cursor(Cursor.HAND_CURSOR)); - try { - BufferedImage originalImage = ImageIO.read(imageFile); - if (originalImage != null) { - Image scaledImage = originalImage.getScaledInstance( - THUMBNAIL_WIDTH - 4, THUMBNAIL_HEIGHT - 4, Image.SCALE_SMOOTH); - button.setIcon(new ImageIcon(scaledImage)); - - // Add click listener to show full image - button.addActionListener(e -> displayFullImage(originalImage, imageFile.getName())); - } - } catch (IOException e) { - log.error("Error creating thumbnail for image {}", imageFile.getName(), e); - button.setText("Error"); + if (image != null) { + Image scaledImage = image.getScaledInstance( + THUMBNAIL_WIDTH - 4, THUMBNAIL_HEIGHT - 4, Image.SCALE_SMOOTH); + button.setIcon(new ImageIcon(scaledImage)); + + // Add click listener to show full image + button.addActionListener(e -> displayFullImage(image, imageName)); + } else { + button.setText("Error"); } // Add hover effects @@ -545,23 +557,25 @@ public class HistoryViewDialog extends JDialog { private void displayFullImage(BufferedImage image, String imageName) { currentSelectedImage = image; - - // Calculate the best fit for the preview area - Dimension previewSize = imagePreviewLabel.getSize(); - if (previewSize.width <= 0 || previewSize.height <= 0) { - previewSize = new Dimension(400, 300); // Default size - } - + + // Calculate the best fit for the preview area. + // Use the container's size (the JScrollPane's viewport) instead of the label's own size + // to avoid a feedback loop where the scaled image size influences the next scaling operation. + Container previewContainer = imagePreviewLabel.getParent(); + Dimension previewSize = (previewContainer != null && previewContainer.getWidth() > 0) + ? previewContainer.getSize() + : new Dimension(400, 300); // Fallback size + double scaleX = (double) previewSize.width / image.getWidth(); double scaleY = (double) previewSize.height / image.getHeight(); double scale = Math.min(scaleX, scaleY); - + int scaledWidth = (int) (image.getWidth() * scale); int scaledHeight = (int) (image.getHeight() * scale); - + Image scaledImage = image.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH); ImageIcon icon = new ImageIcon(scaledImage); - + imagePreviewLabel.setText(""); // Clear text imagePreviewLabel.setIcon(icon); imagePreviewLabel.setToolTipText("Hình ảnh: " + imageName + " (" + image.getWidth() + "x" + image.getHeight() + ")"); @@ -670,21 +684,28 @@ public class HistoryViewDialog extends JDialog { public void setContent(String content) { this.content = content; if (contentArea != null) { - contentArea.setText(content); + setRtfContent(contentArea, content); } } public void setSuggestion(String suggestion) { this.suggestion = suggestion; if (suggestionArea != null) { - suggestionArea.setText(suggestion); + suggestionArea.setText(getDisplayableString(suggestion)); + } + } + + public void setDiagnosis(String diagnosis) { + this.diagnosis = diagnosis; + if (diagnosisArea != null) { + diagnosisArea.setText(getDisplayableString(diagnosis)); } } public void setConclusion(String conclusion) { this.conclusion = conclusion; if (conclusionArea != null) { - conclusionArea.setText(conclusion); + conclusionArea.setText(getDisplayableString(conclusion)); } } @@ -697,4 +718,29 @@ public class HistoryViewDialog extends JDialog { this.servicePrescription = servicePrescription; updatePrescriptionTree(); } + + private void setRtfContent(JTextPane textPane, String rtfContent) { + textPane.setContentType("text/rtf"); + // Check for null, empty, or placeholder RTF content + if (rtfContent == null || rtfContent.isBlank() || rtfContent.contains("\\cf0 null\\par")) { + rtfContent = "{\\rtf1\\ansi\\deff0 {\\fonttbl{\\f0 Arial;}}\\fs24\\i\\cf1 Không có thông tin.\\par}"; // Italic "No information" + } + try { + // The RTF data from the database might be encoded in a specific way. + // Using a stream helps handle character encoding correctly. + textPane.getEditorKit().read(new java.io.ByteArrayInputStream(rtfContent.getBytes()), textPane.getDocument(), 0); + } catch (Exception e) { + log.error("Failed to set RTF content for checkupId {}", checkupId, e); + // Fallback to plain text if RTF parsing fails + textPane.setContentType("text/plain"); + textPane.setText("Không thể hiển thị nội dung chi tiết (lỗi định dạng)."); + } + } + + private String getDisplayableString(String input) { + if (input == null || input.isBlank() || "null".equalsIgnoreCase(input.trim())) { + return "Không có thông tin"; + } + return input; + } } \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/GetImagesByCheckupIdReq.java b/src/main/java/BsK/common/packet/req/GetImagesByCheckupIdReq.java new file mode 100644 index 0000000..d66e904 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/GetImagesByCheckupIdReq.java @@ -0,0 +1,11 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class GetImagesByCheckupIdReq implements Packet { + private String checkupId; +} diff --git a/src/main/java/BsK/common/packet/res/GetImagesByCheckupIdRes.java b/src/main/java/BsK/common/packet/res/GetImagesByCheckupIdRes.java new file mode 100644 index 0000000..3fbac90 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/GetImagesByCheckupIdRes.java @@ -0,0 +1,14 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; +import java.util.List; + +@Data +@AllArgsConstructor +public class GetImagesByCheckupIdRes implements Packet { + private String checkupId; + private List imageNames; + private List imageDatas; +} \ No newline at end of file diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index 2adf7ef..a0127a5 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -134,8 +134,8 @@ public class Server { .addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS)) .addLast(new HttpServerCodec()) .addLast(new ChunkedWriteHandler()) - .addLast(new HttpObjectAggregator(8192)) - .addLast("ws", new WebSocketServerProtocolHandler("/", null, true, 1048576)) + .addLast(new HttpObjectAggregator(50 * 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 3f430a0..e57f0ec 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -11,6 +11,7 @@ import BsK.server.Server; import BsK.server.ServerDashboard; import BsK.server.network.manager.SessionManager; import BsK.server.network.util.UserUtil; +import BsK.server.network.entity.User; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; @@ -33,6 +34,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -48,6 +50,7 @@ import static BsK.server.Server.statement; public class ServerHandler extends SimpleChannelInboundHandler { private static final String IMAGE_DB_PATH = "img_db"; + private static final String CHECKUP_MEDIA_BASE_DIR = "src/main/resources/image/checkup_media"; @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) { @@ -276,7 +279,7 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); while (rs.next()) { - String[] historyEntry = new String[6]; + String[] historyEntry = new String[8]; String checkupDateStr = rs.getString("checkup_date"); try { @@ -312,13 +315,15 @@ public class ServerHandler extends SimpleChannelInboundHandler imageNames = new ArrayList<>(); + List imageDatas = new ArrayList<>(); + + Path checkupDir = Paths.get(IMAGE_DB_PATH, checkupId); + if (Files.exists(checkupDir) && Files.isDirectory(checkupDir)) { + try { + List imagePaths = Files.list(checkupDir) + .filter(path -> { + String filename = path.toString().toLowerCase(); + return filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png") || filename.endsWith(".gif") || filename.endsWith(".bmp"); + }) + .collect(Collectors.toList()); + + for (Path imagePath : imagePaths) { + try { + imageDatas.add(Files.readAllBytes(imagePath)); + imageNames.add(imagePath.getFileName().toString()); + } catch (IOException e) { + log.error("Failed to read image file: {}", imagePath, e); + } + } + } catch (IOException e) { + log.error("Failed to list images for checkupId: {}", checkupId, e); + } + } else { + log.warn("Image directory not found for checkupId: {}", checkupId); + } + + UserUtil.sendPacket(currentUser.getSessionId(), new GetImagesByCheckupIdRes(checkupId, imageNames, imageDatas)); + log.info("Sent {} images for checkupId: {}", imageDatas.size(), checkupId); + } + } } diff --git a/src/main/resources/database/BSK.db b/src/main/resources/database/BSK.db index f1bc837c5f6dddf0da3c2b104ab8149c6007a052..e4928c297f3d857722726fc6b27c84da26102ec1 100644 GIT binary patch delta 889 zcmb`FO=uHA7=~wdc9WfKc9M@~d#E8sd$2)TTkVf{>p^X$AR^dPNQFT?l~O^_P)M@O z!GqKgaH)8eLa=xcSrB>MD)@3U`V=WL-c zb@zZ-MF^+S-YE}EXjL?H<93k?js;f)CxWYjYl4Ra4-2jfP6dw$ZU`O~+!UM%9uqwN zl*5DiaZP>*$J+j#jD9NvOy_sj=KVfXqQID*}^7HHVH)qu8J{R9?f#U;#6 z_$>WL{Z+Nk`&rnSjCPQk;S8ecGKwMdYxH|3XXvm#sxJRNXL&ODe^UZNiXEp?o3t`* zAz?bodswYN{k3y{8!T2*!@g5WW&@`SEg0!mmQraJ8}1Jeh85q~lyj{Gr~gS!PuetA zP?R)L=$m$lEk+Zh8CjxN=#ajvuj{tXpTO`l!b&$;2YUkf*dq<-ubkh^SFHL=AKhSf zuhAK}V1XQ1-Ga5QT#J!TuEF5-rU%J=uRB%vdt-vGF!K@02MQ&N{M!Lbu6B;umg#T7 h_LJ_T^oO>tIllWSC=Jx(BjT9_0^Y3wWXe0(-FSG7)1YJuoP=FH(1Twz&r^8L7E_4 zkRiwv