From a08a7de3c50044e5c9ebf79b48bef01c69a3ffd8 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: Sat, 26 Jul 2025 17:11:23 +0700 Subject: [PATCH] [update] Enhance CheckUpPage and AddDialog with improved ward management and pagination features. Update UI components to reflect changes in address handling, including province and ward selections. Implement debounced search functionality for patient retrieval, optimizing user experience with pagination controls and search parameters in the database queries. --- .../CheckUpPage/AddDialog/AddDialog.java | 280 +++++++++++------- .../ui/component/CheckUpPage/CheckUpPage.java | 28 +- .../packet/req/GetRecentPatientRequest.java | 16 + .../packet/res/GetDistrictResponse.java | 15 - .../packet/res/GetRecentPatientResponse.java | 17 +- .../server/network/handler/ServerHandler.java | 94 +++++- 6 files changed, 312 insertions(+), 138 deletions(-) delete mode 100644 src/main/java/BsK/common/packet/res/GetDistrictResponse.java diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java index 2919959..fc93fa2 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java @@ -55,6 +55,18 @@ public class AddDialog extends JDialog { private JButton addPatientButton; private JButton clearButton; private String targetWard = null; + + // Search and pagination controls (removed search buttons) + private JSpinner pageSpinner; + private JLabel paginationLabel; + private int currentPage = 1; + private int totalPages = 1; + private int totalCount = 0; + private final int pageSize = 20; + + // Debounce search timers + private Timer nameSearchTimer; + private Timer phoneSearchTimer; private void handleAddCheckupResponse(AddCheckupResponse response) { log.info("Received AddCheckupResponse"); @@ -82,21 +94,44 @@ public class AddDialog extends JDialog { } private void sendGetRecentPatientRequest() { - log.info("Sending GetRecentPatientRequest"); + sendPatientSearchRequest(null, null, 1); + } + + private void sendPatientSearchRequest(String searchName, String searchPhone, int page) { + log.info("Sending GetRecentPatientRequest with search: name='{}', phone='{}', page={}", searchName, searchPhone, page); ClientHandler.addResponseListener(GetRecentPatientResponse.class, getRecentPatientResponseListener); - NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecentPatientRequest()); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecentPatientRequest(searchName, searchPhone, page, pageSize)); } private void getRecentPatientHandler(GetRecentPatientResponse response) { log.info("Received GetRecentPatientResponse"); patientData = response.getPatientData(); - + + // Update pagination info + currentPage = response.getCurrentPage(); + totalPages = response.getTotalPages(); + totalCount = response.getTotalCount(); + + // Update table data patientTableModel.setDataVector(patientData, patientColumns); // Set column widths: id, name, birthyear, sdt, address int[] columnWidths = {60, 180, 70, 120, 300}; for (int i = 0; i < columnWidths.length; i++) { patientTable.getColumnModel().getColumn(i).setPreferredWidth(columnWidths[i]); } + + // Update pagination controls + if (pageSpinner != null) { + SpinnerNumberModel model = (SpinnerNumberModel) pageSpinner.getModel(); + model.setMaximum(Math.max(1, totalPages)); + model.setValue(currentPage); + paginationLabel.setText("/ " + totalPages + " (" + totalCount + " bệnh nhân)"); + } + + // Clear selection and update button states + patientTable.clearSelection(); + saveButton.setEnabled(false); + addPatientButton.setEnabled(true); } private int findProvinceIndex(String province) { @@ -126,63 +161,19 @@ public class AddDialog extends JDialog { ClientHandler.deleteListener(AddPatientResponse.class, addPatientResponseListener); ClientHandler.deleteListener(GetRecentPatientResponse.class, getRecentPatientResponseListener); ClientHandler.deleteListener(GetWardResponse.class, wardResponseListener); + + // Clean up timers + if (nameSearchTimer != null && nameSearchTimer.isRunning()) { + nameSearchTimer.stop(); + } + if (phoneSearchTimer != null && phoneSearchTimer.isRunning()) { + phoneSearchTimer.stop(); + } + super.dispose(); } - private void filterPatientTableByPhone() { - String filterText = patientPhoneField.getText().toLowerCase(); - if (patientData == null) { - return; - } - if (filterText.isEmpty()) { - patientTableModel.setDataVector(patientData, patientColumns); - } else { - java.util.List filteredData = new java.util.ArrayList<>(); - for (String[] row : patientData) { - // Assuming phone number is at index 3, based on patientColumns definition - // {"Patient ID", "Patient Name", "Patient Year", "Patient Phone" ,"Patient Address"} - if (row.length > 3 && row[3] != null && row[3].toLowerCase().startsWith(filterText)) { - filteredData.add(row); - } - } - patientTableModel.setDataVector(filteredData.toArray(new String[0][0]), patientColumns); - } - } - private void filterPatientTableByName() { - String filterText = patientNameField.getText().trim(); - if (patientData == null) { - return; - } - if (filterText.isEmpty()) { - patientTableModel.setDataVector(patientData, patientColumns); - patientTable.clearSelection(); // Clear selection when text is empty - saveButton.setEnabled(false); // Disable save button - addPatientButton.setEnabled(true); // Enable add patient button when no patient is selected - } else { - java.util.List filteredData = new java.util.ArrayList<>(); - String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); - for (String[] row : patientData) { - // Assuming patient name is at index 1 - // {"Patient ID", "Patient Name", "Patient Year", "Patient Phone" ,"Patient Address"} - if (row.length > 1 && row[1] != null) { - String cellValue = TextUtils.removeAccents(row[1].toLowerCase()); - if (cellValue.contains(lowerCaseFilterText)) { - filteredData.add(row); - } - } - } - patientTableModel.setDataVector(filteredData.toArray(new String[0][0]), patientColumns); - // If you want to auto-select the first match after filtering, you can add: - // if (!filteredData.isEmpty()) { - // patientTable.setRowSelectionInterval(0, 0); - // saveButton.setEnabled(true); - // } else { - // patientTable.clearSelection(); - // saveButton.setEnabled(false); - // } - } - } public AddDialog(Frame parent) { super(parent, "Thêm Bệnh Nhân", true); @@ -327,7 +318,7 @@ public class AddDialog extends JDialog { patientInfoPanel.add(nameLabel, gbc); gbc.gridx = 1; - gbc.gridwidth = 3; // Span across remaining columns since we removed the plus button + gbc.gridwidth = 3; // Span more columns since we removed the search button gbc.weightx = 1.0; // Allow patient name field to expand patientNameField = new JTextField(15); patientNameField.setFont(textFont); @@ -369,7 +360,7 @@ public class AddDialog extends JDialog { patientGenderField.setPreferredSize(comboBoxSize); patientInfoPanel.add(patientGenderField, gbc); - // Phone (Row 2, Col 0-1) & Patient ID (Row 2, Col 2-3) + // Phone (Row 2, Col 0-2) & Patient ID (Row 3, Col 0-1) gbc.gridy++; gbc.gridx = 0; JLabel phoneLabel = new JLabel("Số điện thoại:"); @@ -377,24 +368,28 @@ public class AddDialog extends JDialog { patientInfoPanel.add(phoneLabel, gbc); gbc.gridx = 1; + gbc.gridwidth = 3; // Span more columns since we removed the search button patientPhoneField = new JTextField(10); patientPhoneField.setFont(textFont); patientPhoneField.setPreferredSize(textFieldSize); patientInfoPanel.add(patientPhoneField, gbc); + gbc.gridwidth = 1; // Reset - gbc.gridx = 2; + // Patient ID (Row 3, Col 0-1) + gbc.gridy++; + gbc.gridx = 0; JLabel patientIdLabel = new JLabel("Mã BN:"); patientIdLabel.setFont(labelFont); patientInfoPanel.add(patientIdLabel, gbc); - gbc.gridx = 3; + gbc.gridx = 1; patientIdField = new JTextField(5); patientIdField.setFont(textFont); patientIdField.setPreferredSize(textFieldSize); patientIdField.setEditable(false); patientInfoPanel.add(patientIdField, gbc); - // CCCD (Row 3, Col 0-1) + // CCCD (Row 4, Col 0-3) gbc.gridy++; gbc.gridx = 0; gbc.gridwidth = 1; @@ -495,7 +490,7 @@ public class AddDialog extends JDialog { addressInfoPanel.add(provinceComboBox, gbc); gbc.gridx = 1; - wardModel = new DefaultComboBoxModel<>(new String[]{"Quận/Huyện"}); + wardModel = new DefaultComboBoxModel<>(new String[]{"Xã/Phường"}); wardComboBox = new JComboBox<>(wardModel); wardComboBox.setFont(textFont); wardComboBox.setPreferredSize(comboBoxSize); @@ -507,51 +502,15 @@ public class AddDialog extends JDialog { gbc.gridwidth = 1; // Reset gridwidth - // Listeners that were previously in the flat layout - patientPhoneField.getDocument().addDocumentListener(new DocumentListener() { - public void changedUpdate(DocumentEvent e) { - filterPatientTableByPhone(); - } - public void removeUpdate(DocumentEvent e) { - filterPatientTableByPhone(); - } - public void insertUpdate(DocumentEvent e) { - filterPatientTableByPhone(); - } - }); - - patientNameField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - handleRowSelection(patientTable.getSelectedRow()); - } - } - }); - - patientNameField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - filterPatientTableByName(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - filterPatientTableByName(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - filterPatientTableByName(); - } - }); + // Setup debounce search functionality + setupDebounceSearch(); // Province ComboBox Listener provinceComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int selectedIndex = provinceComboBox.getSelectedIndex(); - wardModel = new DefaultComboBoxModel<>(new String[]{"Quận/Huyện"}); + wardModel = new DefaultComboBoxModel<>(new String[]{"Xã/Phường"}); wardComboBox.setModel(wardModel); // Set ward combo box model if (selectedIndex != 0) { // If the selected index is not 0 (which corresponds to "Tỉnh/Thành phố") NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetWardRequest(LocalStorage.provinceToId @@ -573,7 +532,32 @@ public class AddDialog extends JDialog { inputScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Usually not needed for forms inputScrollPane.setBorder(BorderFactory.createEmptyBorder()); // Remove scroll pane's own border - JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inputScrollPane, scrollPane); + // Create right panel for table and pagination + JPanel rightPanel = new JPanel(new BorderLayout()); + rightPanel.add(scrollPane, BorderLayout.CENTER); + + // Create pagination panel + JPanel paginationPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel prevLabel = new JLabel("Trang:"); + prevLabel.setFont(labelFont); + paginationPanel.add(prevLabel); + + // Page spinner + SpinnerNumberModel spinnerModel = new SpinnerNumberModel(1, 1, 1, 1); + pageSpinner = new JSpinner(spinnerModel); + pageSpinner.setPreferredSize(new Dimension(60, 25)); + JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) pageSpinner.getEditor(); + editor.getTextField().setHorizontalAlignment(JTextField.CENTER); + paginationPanel.add(pageSpinner); + + // Pagination info label + paginationLabel = new JLabel("/ 1 (0 bệnh nhân)"); + paginationLabel.setFont(textFont); + paginationPanel.add(paginationLabel); + + rightPanel.add(paginationPanel, BorderLayout.SOUTH); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inputScrollPane, rightPanel); splitPane.setResizeWeight(0.5); // Keep or adjust as needed add(splitPane, BorderLayout.CENTER); @@ -641,7 +625,7 @@ public class AddDialog extends JDialog { // Check if ward is selected and enabled if (wardComboBox.isEnabled() && wardComboBox.getSelectedIndex() <= 0) { - JOptionPane.showMessageDialog(this, "Vui lòng chọn quận/huyện"); + JOptionPane.showMessageDialog(this, "Vui lòng chọn xã/phường"); return; } @@ -680,6 +664,14 @@ public class AddDialog extends JDialog { }); clearButton.addActionListener(e -> { + // Stop any ongoing search timers + if (nameSearchTimer != null && nameSearchTimer.isRunning()) { + nameSearchTimer.stop(); + } + if (phoneSearchTimer != null && phoneSearchTimer.isRunning()) { + phoneSearchTimer.stop(); + } + // Clear all fields patientNameField.setText(""); patientPhoneField.setText(""); @@ -710,10 +702,88 @@ public class AddDialog extends JDialog { addPatientButton.setEnabled(true); // Reset table to show all patients - if (patientData != null) { - patientTableModel.setDataVector(patientData, patientColumns); + sendGetRecentPatientRequest(); + }); + + + + // Add pagination spinner listener + pageSpinner.addChangeListener(e -> { + int selectedPage = (Integer) pageSpinner.getValue(); + if (selectedPage != currentPage && selectedPage >= 1 && selectedPage <= totalPages) { + // Determine current search parameters + String searchName = patientNameField.getText().trim(); + String searchPhone = patientPhoneField.getText().trim(); + searchName = searchName.isEmpty() ? null : searchName; + searchPhone = searchPhone.isEmpty() ? null : searchPhone; + + sendPatientSearchRequest(searchName, searchPhone, selectedPage); + } + }); +} + + private void setupDebounceSearch() { + // Initialize timers for debounce search (1 second delay) + nameSearchTimer = new Timer(1000, e -> { + String searchName = patientNameField.getText().trim(); + String searchPhone = patientPhoneField.getText().trim(); + searchName = searchName.isEmpty() ? null : searchName; + searchPhone = searchPhone.isEmpty() ? null : searchPhone; + sendPatientSearchRequest(searchName, searchPhone, 1); + }); + nameSearchTimer.setRepeats(false); + + phoneSearchTimer = new Timer(1000, e -> { + String searchName = patientNameField.getText().trim(); + String searchPhone = patientPhoneField.getText().trim(); + searchName = searchName.isEmpty() ? null : searchName; + searchPhone = searchPhone.isEmpty() ? null : searchPhone; + sendPatientSearchRequest(searchName, searchPhone, 1); + }); + phoneSearchTimer.setRepeats(false); + + // Add DocumentListener to patientNameField for debounced search + patientNameField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + resetAndStartTimer(nameSearchTimer); + } + + @Override + public void removeUpdate(DocumentEvent e) { + resetAndStartTimer(nameSearchTimer); + } + + @Override + public void changedUpdate(DocumentEvent e) { + resetAndStartTimer(nameSearchTimer); } }); + + // Add DocumentListener to patientPhoneField for debounced search + patientPhoneField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + resetAndStartTimer(phoneSearchTimer); + } + + @Override + public void removeUpdate(DocumentEvent e) { + resetAndStartTimer(phoneSearchTimer); + } + + @Override + public void changedUpdate(DocumentEvent e) { + resetAndStartTimer(phoneSearchTimer); + } + }); + } + + private void resetAndStartTimer(Timer timer) { + if (timer.isRunning()) { + timer.stop(); + } + timer.start(); } private void handleRowSelection(int selectedRow) { 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 17f76fa..f7a3f14 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -758,14 +758,26 @@ public class CheckUpPage extends JPanel { patientInfoInnerPanel.add(customerAddressField, gbcPatient); // Row 7: Location dropdowns + gbcPatient.gridx = 0; gbcPatient.gridy = 7; + gbcPatient.gridwidth = 1; // Reset gridwidth + JLabel provinceLabel = new JLabel("Tỉnh/Thành phố", SwingConstants.RIGHT); + provinceLabel.setFont(labelFont); + patientInfoInnerPanel.add(provinceLabel, gbcPatient); + gbcPatient.gridwidth = 1; // Reset gridwidth gbcPatient.gridx = 1; gbcPatient.gridy = 7; provinceComboBox = new JComboBox<>(LocalStorage.provinces); provinceComboBox.setFont(fieldFont); patientInfoInnerPanel.add(provinceComboBox, gbcPatient); + + // Add Xã/Phường label before wardComboBox + gbcPatient.gridx = 2; gbcPatient.gridy = 7; + JLabel wardLabel = new JLabel("Xã/Phường", SwingConstants.RIGHT); + wardLabel.setFont(labelFont); + patientInfoInnerPanel.add(wardLabel, gbcPatient); - gbcPatient.gridx = 2; - wardModel = new DefaultComboBoxModel<>(new String[]{"Huyện"}); + gbcPatient.gridx = 3; + wardModel = new DefaultComboBoxModel<>(new String[]{"Xã/Phường"}); wardComboBox = new JComboBox<>(wardModel); wardComboBox.setFont(fieldFont); wardComboBox.setEnabled(false); @@ -830,12 +842,12 @@ public class CheckUpPage extends JPanel { NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetWardRequest(provinceId)); wardComboBox.setEnabled(false); wardModel.removeAllElements(); - wardModel.addElement("Đang tải quận/huyện..."); + wardModel.addElement("Đang tải xã/phường..."); } } else { wardComboBox.setEnabled(false); wardModel.removeAllElements(); - wardModel.addElement("Huyện"); + wardModel.addElement("Xã/Phường"); } }); @@ -2270,19 +2282,19 @@ public class CheckUpPage extends JPanel { // Ward will be set in the response handler using targetWard } else { log.warn("Province not found: {}", province); - wardModel.removeAllElements(); wardModel.addElement("Huyện"); wardComboBox.setEnabled(false); + wardModel.removeAllElements(); wardModel.addElement("Xã/Phường"); wardComboBox.setEnabled(false); targetWard = null; } } else if (addressParts.length == 1) { // Handle case with only one part customerAddressField.setText(fullAddress); provinceComboBox.setSelectedIndex(0); - wardModel.removeAllElements(); wardModel.addElement("Huyện"); wardComboBox.setEnabled(false); + wardModel.removeAllElements(); wardModel.addElement("Xã/Phường"); wardComboBox.setEnabled(false); } else { // Empty address case customerAddressField.setText(""); provinceComboBox.setSelectedIndex(0); - wardModel.removeAllElements(); wardModel.addElement("Huyện"); wardComboBox.setEnabled(false); + wardModel.removeAllElements(); wardModel.addElement("Xã/Phường"); wardComboBox.setEnabled(false); } // Setup for Supersonic View media for the NEWLY selected patient @@ -4487,7 +4499,7 @@ public class CheckUpPage extends JPanel { String province = (String) provinceComboBox.getSelectedItem(); String fullAddress = String.format("%s, %s, %s, %s", address, - ward != null && !ward.equals("Huyện") ? ward : "", + ward != null && !ward.equals("Xã/Phường") ? ward : "", province != null && !province.equals("Tỉnh/Thành phố") ? province : "" ).replaceAll(", ,", ",").trim().replaceAll(",$", ""); diff --git a/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java b/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java index e33d93f..dc6d55a 100644 --- a/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java +++ b/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java @@ -3,10 +3,26 @@ package BsK.common.packet.req; import BsK.common.packet.Packet; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Data @AllArgsConstructor +@NoArgsConstructor public class GetRecentPatientRequest implements Packet { + private String searchName; + private String searchPhone; + private int page = 1; + private int pageSize = 20; + + // Constructor for backward compatibility (gets first page with default page size) + public GetRecentPatientRequest(int page, int pageSize) { + this(null, null, page, pageSize); + } + + // Constructor for search without pagination + public GetRecentPatientRequest(String searchName, String searchPhone) { + this(searchName, searchPhone, 1, 20); + } } diff --git a/src/main/java/BsK/common/packet/res/GetDistrictResponse.java b/src/main/java/BsK/common/packet/res/GetDistrictResponse.java deleted file mode 100644 index 838740d..0000000 --- a/src/main/java/BsK/common/packet/res/GetDistrictResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package BsK.common.packet.res; - -import BsK.common.packet.Packet; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - -import java.util.HashMap; - -@Slf4j -@Data -@AllArgsConstructor -public class GetWardResponse implements Packet { - String[] wards; -} diff --git a/src/main/java/BsK/common/packet/res/GetRecentPatientResponse.java b/src/main/java/BsK/common/packet/res/GetRecentPatientResponse.java index 7dc4a31..e9facbb 100644 --- a/src/main/java/BsK/common/packet/res/GetRecentPatientResponse.java +++ b/src/main/java/BsK/common/packet/res/GetRecentPatientResponse.java @@ -3,11 +3,26 @@ package BsK.common.packet.res; import BsK.common.packet.Packet; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @AllArgsConstructor +@NoArgsConstructor @Data public class GetRecentPatientResponse implements Packet { - String [][] patientData; + String[][] patientData; + int totalCount; + int currentPage; + int totalPages; + int pageSize; + + // Constructor for backward compatibility + public GetRecentPatientResponse(String[][] patientData) { + this.patientData = patientData; + this.totalCount = patientData != null ? patientData.length : 0; + this.currentPage = 1; + this.totalPages = 1; + this.pageSize = this.totalCount; + } } diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index 0db4f75..24ad37f 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -472,14 +472,87 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); while (rs.next()) { @@ -498,16 +571,19 @@ public class ServerHandler extends SimpleChannelInboundHandler