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 f1bc837..e4928c2 100644 Binary files a/src/main/resources/database/BSK.db and b/src/main/resources/database/BSK.db differ