[update] Enhance CheckUpPage and AddDialog by integrating new patient fields including CCCD/DDCN, heart rate, and blood pressure measurements. Revise layout for improved data entry and user experience, ensuring comprehensive patient information management. Update data structures and server handling to accommodate new fields, enhancing overall functionality.

This commit is contained in:
Lê Thành Đạt committed 2025-07-10 01:51:05 +07:00
1 parent f816ce64b2
commit 2f5d2bcf1b
9 files changed
+1241 -1006

No files matched your search

@@ -35,6 +35,7 @@ public class AddDialog extends JDialog {
private JTextField patientYearField;
private JTextField patientIdField;
private JTextField patientPhoneField;
private JTextField cccdField;
private JComboBox patientGenderField;
private JComboBox wardComboBox, districtComboBox, provinceComboBox;
private JTextField customerAddressField;
@@ -172,11 +173,18 @@ public class AddDialog extends JDialog {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// Set size of the dialog
setSize(1000, 400);
setSize(1200, 700);
// Put in the middle of parent window
setLocationRelativeTo(parent);
setResizable(true);
// UI Fonts and Dimensions
Font labelFont = new Font("Arial", Font.BOLD, 15);
Font textFont = new Font("Arial", Font.PLAIN, 15);
Font titleFont = new Font("Arial", Font.BOLD, 16);
Dimension textFieldSize = new Dimension(100, 30);
Dimension comboBoxSize = new Dimension(100, 30);
// Ensure modal behavior and proper parent relationship
setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
setAlwaysOnTop(false); // Don't force always on top, let modal behavior handle this
@@ -215,8 +223,8 @@ public class AddDialog extends JDialog {
};
patientTable = new JTable(patientTableModel);
patientTable.setFont(new Font("Serif", Font.PLAIN, 14));
patientTable.setRowHeight(30);
patientTable.setFont(textFont);
patientTable.setRowHeight(35);
patientTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(patientTable);
@@ -250,7 +258,7 @@ public class AddDialog extends JDialog {
patientInfoPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Thông tin bệnh nhân",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
titleFont, new Color(50, 50, 50)
));
mainGbc.gridx = 0;
mainGbc.gridy = 0;
@@ -262,7 +270,7 @@ public class AddDialog extends JDialog {
addressInfoPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Địa chỉ",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
titleFont, new Color(50, 50, 50)
));
mainGbc.gridy = 1;
inputPanel.add(addressInfoPanel, mainGbc);
@@ -272,7 +280,7 @@ public class AddDialog extends JDialog {
checkupInfoPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Thông tin đăng ký khám",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
titleFont, new Color(50, 50, 50)
));
mainGbc.gridy = 2;
inputPanel.add(checkupInfoPanel, mainGbc);
@@ -294,20 +302,25 @@ public class AddDialog extends JDialog {
gbc.gridy = 0;
gbc.gridx = 0;
gbc.gridwidth = 1;
patientInfoPanel.add(new JLabel("Họ và tên:"), gbc);
JLabel nameLabel = new JLabel("Họ và tên:");
nameLabel.setFont(labelFont);
patientInfoPanel.add(nameLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0; // Allow patient name field to expand
patientNameField = new JTextField(15);
patientNameField.setFont(textFont);
patientNameField.setPreferredSize(textFieldSize);
patientInfoPanel.add(patientNameField, gbc);
gbc.weightx = 0.0; // Reset
gbc.gridx = 2;
gbc.gridwidth = 1;
ImageIcon originalIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/add.png");
Image scaledImage = originalIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH);
Image scaledImage = originalIcon.getImage().getScaledInstance(24, 24, Image.SCALE_SMOOTH);
ImageIcon addIcon = new ImageIcon(scaledImage);
JButton addButton = new JButton(addIcon);
addButton.setPreferredSize(new Dimension(40, 30));
addButton.setToolTipText("Thêm bệnh nhân mới (nếu không tìm thấy)");
patientInfoPanel.add(addButton, gbc);
@@ -315,7 +328,9 @@ public class AddDialog extends JDialog {
gbc.gridy++;
gbc.gridx = 0;
gbc.gridwidth = 1;
patientInfoPanel.add(new JLabel("Ngày sinh:"), gbc);
JLabel dobLabel = new JLabel("Ngày sinh:");
dobLabel.setFont(labelFont);
patientInfoPanel.add(dobLabel, gbc);
gbc.gridx = 1;
UtilDateModel model = new UtilDateModel();
@@ -325,33 +340,62 @@ public class AddDialog extends JDialog {
p.put("text.year", "Năm");
JDatePanelImpl datePanel = new JDatePanelImpl(model, p);
dobPicker = new JDatePickerImpl(datePanel, new DateLabelFormatter());
dobPicker.getJFormattedTextField().setFont(textFont);
dobPicker.setPreferredSize(new Dimension(150, 30));
patientInfoPanel.add(dobPicker, gbc);
gbc.gridx = 2;
patientInfoPanel.add(new JLabel("Giới tính:"), gbc);
JLabel genderLabel = new JLabel("Giới tính:");
genderLabel.setFont(labelFont);
patientInfoPanel.add(genderLabel, gbc);
gbc.gridx = 3;
patientGenderField = new JComboBox<>(new String[]{"Nam", "Nữ"});
patientGenderField.setFont(textFont);
patientGenderField.setPreferredSize(comboBoxSize);
patientInfoPanel.add(patientGenderField, gbc);
// Phone (Row 2, Col 0-1) & Patient ID (Row 2, Col 2-3)
gbc.gridy++;
gbc.gridx = 0;
patientInfoPanel.add(new JLabel("Số điện thoại:"), gbc);
JLabel phoneLabel = new JLabel("Số điện thoại:");
phoneLabel.setFont(labelFont);
patientInfoPanel.add(phoneLabel, gbc);
gbc.gridx = 1;
patientPhoneField = new JTextField(10);
patientPhoneField.setFont(textFont);
patientPhoneField.setPreferredSize(textFieldSize);
patientInfoPanel.add(patientPhoneField, gbc);
gbc.gridx = 2;
patientInfoPanel.add(new JLabel("Mã BN:"), gbc);
JLabel patientIdLabel = new JLabel("Mã BN:");
patientIdLabel.setFont(labelFont);
patientInfoPanel.add(patientIdLabel, gbc);
gbc.gridx = 3;
patientIdField = new JTextField(5);
patientIdField.setFont(textFont);
patientIdField.setPreferredSize(textFieldSize);
patientIdField.setEditable(false);
patientInfoPanel.add(patientIdField, gbc);
// CCCD (Row 3, Col 0-1)
gbc.gridy++;
gbc.gridx = 0;
gbc.gridwidth = 1;
JLabel cccdLabel = new JLabel("CCCD/DDCN:");
cccdLabel.setFont(labelFont);
patientInfoPanel.add(cccdLabel, gbc);
gbc.gridx = 1;
gbc.gridwidth = 3; // Span across remaining columns
cccdField = new JTextField(15);
cccdField.setFont(textFont);
cccdField.setPreferredSize(textFieldSize);
patientInfoPanel.add(cccdField, gbc);
gbc.gridwidth = 1; // Reset
// Add event listener to the addButton (moved here as it's part of this panel now)
addButton.addActionListener(e -> {
if (!patientIdField.getText().isEmpty()) {
@@ -366,6 +410,7 @@ public class AddDialog extends JDialog {
String district = (String) districtComboBox.getSelectedItem();
String ward = (String) wardComboBox.getSelectedItem();
String address = String.join(", ", customerAddress, ward, district, province);
String cccd = cccdField.getText();
//split name into first name and last name
String[] nameParts = patientName.split(" ");
@@ -392,7 +437,7 @@ public class AddDialog extends JDialog {
Date date = dateFormat.parse(dateText);
long timestamp = date.getTime(); // This converts the Date to milliseconds since epoch (long)
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new AddPatientRequest(firstName, lastNameStr, timestamp,
patientPhone, address, patientGender));
patientPhone, address, patientGender, cccd));
} catch (ParseException pe) {
pe.printStackTrace();
JOptionPane.showMessageDialog(null, "Invalid date format: " + dateText);
@@ -405,25 +450,33 @@ public class AddDialog extends JDialog {
gbc.gridx = 0;
gbc.gridwidth = 1;
gbc.weightx = 0.0; // Label doesn't expand
checkupInfoPanel.add(new JLabel("Bác sĩ khám:"), gbc);
JLabel doctorLabel = new JLabel("Bác sĩ khám:");
doctorLabel.setFont(labelFont);
checkupInfoPanel.add(doctorLabel, gbc);
gbc.gridx = 1;
gbc.gridwidth = 1; // Doctor ComboBox takes 1 column
gbc.weightx = 0.5; // Doctor ComboBox takes some horizontal space
doctorComboBox = new JComboBox<>(LocalStorage.doctorsName != null ? LocalStorage.doctorsName : new String[]{"Đang tải..."});
doctorComboBox.setFont(textFont);
doctorComboBox.setPreferredSize(comboBoxSize);
checkupInfoPanel.add(doctorComboBox, gbc);
// Checkup Type on the same row
gbc.gridx = 2;
gbc.gridwidth = 1;
gbc.weightx = 0.0; // Label doesn't expand
checkupInfoPanel.add(new JLabel("Loại khám:"), gbc);
JLabel checkupTypeLabel = new JLabel("Loại khám:");
checkupTypeLabel.setFont(labelFont);
checkupInfoPanel.add(checkupTypeLabel, gbc);
gbc.gridx = 3;
gbc.gridwidth = 1; // Checkup Type ComboBox takes 1 column
gbc.weightx = 0.5; // Checkup Type ComboBox takes some horizontal space
String[] checkupTypeOptions = {"BENH", "THAI", "KHAC"};
String[] checkupTypeOptions = {"BỆNH", "THAI", "KHÁC"};
checkupTypeComboBox = new JComboBox<>(checkupTypeOptions);
checkupTypeComboBox.setFont(textFont);
checkupTypeComboBox.setPreferredSize(comboBoxSize);
checkupInfoPanel.add(checkupTypeComboBox, gbc);
gbc.weightx = 0.0; // Reset weightx
@@ -433,13 +486,17 @@ public class AddDialog extends JDialog {
gbc.gridy = 0;
gbc.gridx = 0;
gbc.gridwidth = 1;
gbc.weightx = 0.0;
addressInfoPanel.add(new JLabel("Địa chỉ cụ thể:"), gbc); // Changed label slightly for clarity
gbc.weightx = 0.0;
JLabel addressLabel = new JLabel("Địa chỉ cụ thể:");
addressLabel.setFont(labelFont);
addressInfoPanel.add(addressLabel, gbc); // Changed label slightly for clarity
gbc.gridx = 1;
gbc.gridwidth = 3;
gbc.weightx = 1.0;
customerAddressField = new JTextField(20);
customerAddressField.setFont(textFont);
customerAddressField.setPreferredSize(textFieldSize);
addressInfoPanel.add(customerAddressField, gbc);
gbc.weightx = 0.0;
@@ -449,17 +506,23 @@ public class AddDialog extends JDialog {
gbc.gridwidth = 1; // Each combo box takes 1 logical column in this setup
gbc.weightx = 0.33; // Distribute space among the three combo boxes
provinceComboBox = new JComboBox<>(LocalStorage.provinces != null ? LocalStorage.provinces : new String[]{"Tỉnh/TP"});
provinceComboBox.setFont(textFont);
provinceComboBox.setPreferredSize(comboBoxSize);
addressInfoPanel.add(provinceComboBox, gbc);
gbc.gridx = 1;
districtModel = new DefaultComboBoxModel<>(new String[]{"Quận/Huyện"});
districtComboBox = new JComboBox<>(districtModel);
districtComboBox.setFont(textFont);
districtComboBox.setPreferredSize(comboBoxSize);
districtComboBox.setEnabled(false);
addressInfoPanel.add(districtComboBox, gbc);
gbc.gridx = 2;
wardModel = new DefaultComboBoxModel<>(new String[]{"Phường/Xã"});
wardComboBox = new JComboBox<>(wardModel);
wardComboBox.setFont(textFont);
wardComboBox.setPreferredSize(comboBoxSize);
wardComboBox.setEnabled(false);
addressInfoPanel.add(wardComboBox, gbc);
gbc.weightx = 0.0; // Reset weightx for any subsequent components in this panel
@@ -548,18 +611,20 @@ public class AddDialog extends JDialog {
inputScrollPane.setBorder(BorderFactory.createEmptyBorder()); // Remove scroll pane's own border
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inputScrollPane, scrollPane);
splitPane.setResizeWeight(0.6); // Keep or adjust as needed
splitPane.setResizeWeight(0.5); // Keep or adjust as needed
add(splitPane, BorderLayout.CENTER);
JPanel ButtonPanel = new JPanel();
JButton closeButton = new JButton("Close");
closeButton.setFont(labelFont);
closeButton.addActionListener(e -> {
setVisible(false);
});
ButtonPanel.add(closeButton);
saveButton = new JButton("Add to Checkup");
saveButton.setFont(labelFont);
saveButton.addActionListener(e -> {
int patientId = Integer.parseInt(patientIdField.getText());
int doctorId = doctorComboBox.getSelectedIndex()+ 1;
@@ -585,10 +650,12 @@ public class AddDialog extends JDialog {
String patientAddress = patientTable.getValueAt(selectedRow, 4).toString();
String patientGender = patientData[selectedRow][5];
String patientDob = patientData[selectedRow][6];
String patientCccd = patientData[selectedRow][7];
patientIdField.setText(patientId);
patientNameField.setText(patientName);
patientPhoneField.setText(patientPhone);
patientGenderField.setSelectedItem(patientGender);
cccdField.setText(patientCccd);
// Extract province, district, ward from the address
String[] addressParts = patientAddress.split(", ");
@@ -142,11 +142,11 @@ public class CheckUpPage extends JPanel {
private final ResponseListener<GetOrderInfoByCheckupRes> orderInfoByCheckupListener = this::handleGetOrderInfoByCheckupResponse;
private final ResponseListener<GetAllTemplatesRes> getAllTemplatesListener = this::handleGetAllTemplatesResponse;
private final ResponseListener<UploadCheckupImageResponse> uploadImageResponseListener = this::handleUploadImageResponse;
private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField;
private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField, customerCccdDdcnField;
private JTextArea suggestionField, diagnosisField, conclusionField; // Changed symptomsField to suggestionField
private JTextPane notesField;
private JComboBox<String> doctorComboBox, statusComboBox, genderComboBox, provinceComboBox, districtComboBox, wardComboBox, checkupTypeComboBox, templateComboBox;
private JSpinner customerWeightSpinner, customerHeightSpinner;
private JSpinner customerWeightSpinner, customerHeightSpinner, patientHeartRateSpinner, bloodPressureSystolicSpinner, bloodPressureDiastolicSpinner;
private JDatePickerImpl datePicker, dobPicker, recheckupDatePicker;
private String[][] medicinePrescription = new String[0][0]; // Initialize to empty
private String[][] servicePrescription = new String[0][0]; // Initialize to empty
@@ -504,8 +504,20 @@ public class CheckUpPage extends JPanel {
gbcPatient.fill = GridBagConstraints.HORIZONTAL;
gbcPatient.anchor = GridBagConstraints.WEST;
// Row 1: Name
// Row 0: CCCD/DDCN
gbcPatient.gridx = 0; gbcPatient.gridy = 0; gbcPatient.weightx = 0.1;
JLabel cccdLabel = new JLabel("CCCD/DDCN", SwingConstants.RIGHT);
cccdLabel.setFont(labelFont);
patientInfoInnerPanel.add(cccdLabel, gbcPatient);
gbcPatient.gridx = 1; gbcPatient.weightx = 0.4; gbcPatient.gridwidth = 3;
customerCccdDdcnField = new JTextField(15);
customerCccdDdcnField.setFont(fieldFont);
patientInfoInnerPanel.add(customerCccdDdcnField, gbcPatient);
gbcPatient.gridwidth = 1; // Reset gridwidth
// Row 1: Name
gbcPatient.gridx = 0; gbcPatient.gridy = 1; gbcPatient.weightx = 0.1;
JLabel hoLabel = new JLabel("Họ", SwingConstants.RIGHT);
hoLabel.setFont(labelFont);
patientInfoInnerPanel.add(hoLabel, gbcPatient);
@@ -526,7 +538,7 @@ public class CheckUpPage extends JPanel {
patientInfoInnerPanel.add(customerFirstNameField, gbcPatient);
// Row 2: ID and Phone
gbcPatient.gridx = 0; gbcPatient.gridy = 1; gbcPatient.weightx = 0.1;
gbcPatient.gridx = 0; gbcPatient.gridy = 2; gbcPatient.weightx = 0.1;
JLabel idLabel = new JLabel("Mã BN", SwingConstants.RIGHT);
idLabel.setFont(labelFont);
patientInfoInnerPanel.add(idLabel, gbcPatient);
@@ -553,7 +565,7 @@ public class CheckUpPage extends JPanel {
patientInfoInnerPanel.add(customerPhoneField, gbcPatient);
// Row 3: Gender and DOB
gbcPatient.gridx = 0; gbcPatient.gridy = 2;
gbcPatient.gridx = 0; gbcPatient.gridy = 3;
JLabel genderLabel = new JLabel("Giới tính", SwingConstants.RIGHT);
genderLabel.setFont(labelFont);
patientInfoInnerPanel.add(genderLabel, gbcPatient);
@@ -581,7 +593,7 @@ public class CheckUpPage extends JPanel {
patientInfoInnerPanel.add(dobPicker, gbcPatient);
// Row 4: Weight and Height
gbcPatient.gridx = 0; gbcPatient.gridy = 3;
gbcPatient.gridx = 0; gbcPatient.gridy = 4;
JLabel weightLabel = new JLabel("Cân nặng (kg)", SwingConstants.RIGHT);
weightLabel.setFont(labelFont);
patientInfoInnerPanel.add(weightLabel, gbcPatient);
@@ -611,8 +623,58 @@ public class CheckUpPage extends JPanel {
}
patientInfoInnerPanel.add(customerHeightSpinner, gbcPatient);
// Row 5: Address (full width)
gbcPatient.gridx = 0; gbcPatient.gridy = 4;
// Row 5: Heart Rate and Blood Pressure
gbcPatient.gridy++;
gbcPatient.gridx = 0;
JLabel heartRateLabel = new JLabel("Nhịp tim (l/p)", SwingConstants.RIGHT);
heartRateLabel.setFont(labelFont);
patientInfoInnerPanel.add(heartRateLabel, gbcPatient);
gbcPatient.gridx = 1;
SpinnerModel heartRateModel = new SpinnerNumberModel(80, 30, 250, 1);
patientHeartRateSpinner = new JSpinner(heartRateModel);
patientHeartRateSpinner.setFont(fieldFont);
JComponent heartRateEditor = patientHeartRateSpinner.getEditor();
if (heartRateEditor instanceof JSpinner.DefaultEditor) {
((JSpinner.DefaultEditor)heartRateEditor).getTextField().setFont(fieldFont);
}
patientInfoInnerPanel.add(patientHeartRateSpinner, gbcPatient);
gbcPatient.gridx = 2;
JLabel bloodPressureLabel = new JLabel("Huyết áp (mmHg)", SwingConstants.RIGHT);
bloodPressureLabel.setFont(labelFont);
patientInfoInnerPanel.add(bloodPressureLabel, gbcPatient);
gbcPatient.gridx = 3;
JPanel bloodPressurePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
bloodPressurePanel.setOpaque(false);
SpinnerModel systolicModel = new SpinnerNumberModel(120, 50, 300, 1);
bloodPressureSystolicSpinner = new JSpinner(systolicModel);
bloodPressureSystolicSpinner.setFont(fieldFont);
JComponent systolicEditor = bloodPressureSystolicSpinner.getEditor();
if (systolicEditor instanceof JSpinner.DefaultEditor) {
((JSpinner.DefaultEditor)systolicEditor).getTextField().setFont(fieldFont);
}
SpinnerModel diastolicModel = new SpinnerNumberModel(80, 30, 200, 1);
bloodPressureDiastolicSpinner = new JSpinner(diastolicModel);
bloodPressureDiastolicSpinner.setFont(fieldFont);
JComponent diastolicEditor = bloodPressureDiastolicSpinner.getEditor();
if (diastolicEditor instanceof JSpinner.DefaultEditor) {
((JSpinner.DefaultEditor)diastolicEditor).getTextField().setFont(fieldFont);
}
JLabel slashLabel = new JLabel(" / ");
slashLabel.setFont(fieldFont);
bloodPressurePanel.add(bloodPressureSystolicSpinner);
bloodPressurePanel.add(slashLabel);
bloodPressurePanel.add(bloodPressureDiastolicSpinner);
patientInfoInnerPanel.add(bloodPressurePanel, gbcPatient);
// Row 6: Address (full width)
gbcPatient.gridx = 0; gbcPatient.gridy = 6;
JLabel addressLabel = new JLabel("Địa chỉ", SwingConstants.RIGHT);
addressLabel.setFont(labelFont);
patientInfoInnerPanel.add(addressLabel, gbcPatient);
@@ -622,9 +684,9 @@ public class CheckUpPage extends JPanel {
customerAddressField.setFont(fieldFont);
patientInfoInnerPanel.add(customerAddressField, gbcPatient);
// Row 6: Location dropdowns
// Row 7: Location dropdowns
gbcPatient.gridwidth = 1; // Reset gridwidth
gbcPatient.gridx = 1; gbcPatient.gridy = 5;
gbcPatient.gridx = 1; gbcPatient.gridy = 7;
provinceComboBox = new JComboBox<>(LocalStorage.provinces);
provinceComboBox.setFont(fieldFont);
patientInfoInnerPanel.add(provinceComboBox, gbcPatient);
@@ -690,7 +752,7 @@ public class CheckUpPage extends JPanel {
roomControlPanel.add(freeRoomButton, gbcRoom);
// Add room control panel to the bottom of patient info panel
gbcPatient.gridx = 0; gbcPatient.gridy = 6; gbcPatient.gridwidth = 4;
gbcPatient.gridx = 0; gbcPatient.gridy = 8; gbcPatient.gridwidth = 4;
patientInfoInnerPanel.add(roomControlPanel, gbcPatient);
// Add back the event listeners
@@ -1732,6 +1794,7 @@ public class CheckUpPage extends JPanel {
try { customerHeightSpinner.setValue(Double.parseDouble(selectedPatient.getCustomerHeight())); } catch (NumberFormatException e) {log.warn("Invalid height: {}", selectedPatient.getCustomerHeight()); customerHeightSpinner.setValue(0.0);}
genderComboBox.setSelectedItem(selectedPatient.getCustomerGender());
checkupTypeComboBox.setSelectedItem(selectedPatient.getCheckupType());
customerCccdDdcnField.setText(selectedPatient.getCccdDdcn() != null ? selectedPatient.getCccdDdcn() : "");
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetPatientHistoryRequest(Integer.parseInt(selectedPatient.getCustomerId())));
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetOrderInfoByCheckupReq(selectedPatient.getCheckupId()));
@@ -3145,6 +3208,7 @@ public class CheckUpPage extends JPanel {
String status = (String) statusComboBox.getSelectedItem();
String checkupType = (String) checkupTypeComboBox.getSelectedItem();
String reCheckupDate = recheckupDatePicker.getJFormattedTextField().getText();
String customerCccdDdcn = customerCccdDdcnField.getText();
// Log all fields that will be sent to backend
log.info("=== SaveCheckupRequest Data ===");
@@ -3169,6 +3233,7 @@ public class CheckUpPage extends JPanel {
log.info("medicinePrescription: {}", (Object) medicinePrescription);
log.info("servicePrescription: {}", (Object) servicePrescription);
log.info("reCheckupDate: {}", reCheckupDate);
log.info("customerCccdDdcn: {}", customerCccdDdcn);
log.info("=== End SaveCheckupRequest Data ===");
@@ -3192,6 +3257,7 @@ public class CheckUpPage extends JPanel {
checkupType,
conclusion, // Ket luan
reCheckupDate,
customerCccdDdcn,
medicinePrescription,
servicePrescription
);
@@ -3280,6 +3346,7 @@ public class CheckUpPage extends JPanel {
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
customerCccdDdcnField.setText("");
if (doctorComboBox.getItemCount() > 0) doctorComboBox.setSelectedIndex(0);
statusComboBox.setSelectedIndex(0);
@@ -3291,6 +3358,9 @@ public class CheckUpPage extends JPanel {
customerWeightSpinner.setValue(0);
customerHeightSpinner.setValue(0);
patientHeartRateSpinner.setValue(80);
bloodPressureSystolicSpinner.setValue(120);
bloodPressureDiastolicSpinner.setValue(80);
datePicker.getModel().setValue(null);
dobPicker.getModel().setValue(null);
File diff suppressed because it is too large. Load diff
@@ -1,646 +0,0 @@
package BsK.client.ui.component.CheckoutPage;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import BsK.client.network.handler.ClientHandler;
import BsK.client.network.handler.ResponseListener;
import BsK.common.packet.req.GetMedInfoRequest;
import BsK.common.packet.res.GetMedInfoResponse;
import BsK.common.util.network.NetworkUtil;
import BsK.common.util.text.TextUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Slf4j
public class StandaloneMedicineDialog extends JDialog {
private JTextField medicineNameField;
private JTextField barcodeField; // New field for barcode
private JTextField medicineCompanyField;
private JTextArea medicineDescriptionField;
private JSpinner quantitySpinner;
private JTextField quantityLeftField;
private JComboBox<String> UnitComboBox;
private JTextField priceField;
private JTextField totalField;
private JSpinner morningSpinner, noonSpinner, eveningSpinner;
private JTextArea noteField;
private DefaultTableModel tableModel, selectedTableModel;
private String[] medcineColumns = {"ID", "Tên thuốc", "Barcode", "Công ty", "Mô tả", "Tồn kho", "ĐVT", "Giá"}; // Added Barcode Column
private String[][] medicineData;
private final ResponseListener<GetMedInfoResponse> getMedInfoResponseListener = this::getMedInfoHandler;
private TableColumnModel columnModel;
private HashMap<String, Boolean> selectedMedicine = new HashMap<>();
private JTable medicineTable;
private JTable selectedTable;
private boolean isProgrammaticallySettingMedicineNameField = false;
private String[][] medicinePrescription;
private static final Logger logger = LoggerFactory.getLogger(StandaloneMedicineDialog.class);
public String[][] getMedicinePrescription() {
return medicinePrescription;
}
void getMedInfoHandler(GetMedInfoResponse response) {
logger.info("Received medicine data for Standalone Dialog");
medicineData = response.getMedInfo(); // Assuming GetMedInfoResponse also returns barcode in medInfo array
tableModel.setDataVector(medicineData, medcineColumns);
resizeMedicineTableColumns();
}
private void sendGetMedInfoRequest() {
logger.info("Sending GetMedInfoRequest for Standalone Dialog");
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetMedInfoRequest());
}
public StandaloneMedicineDialog(Frame parent) {
super(parent, "Thêm thuốc vào hóa đơn (khách lẻ)", true);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(1250, 800);
setLocationRelativeTo(parent);
setResizable(true);
ClientHandler.addResponseListener(GetMedInfoResponse.class, getMedInfoResponseListener);
sendGetMedInfoRequest();
setLayout(new BorderLayout(10, 10));
JPanel mainInputPanel = new JPanel(new GridBagLayout());
mainInputPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
GridBagConstraints mainGbc = new GridBagConstraints();
mainGbc.fill = GridBagConstraints.HORIZONTAL;
mainGbc.anchor = GridBagConstraints.NORTHWEST;
mainGbc.insets = new Insets(5, 5, 5, 5);
mainGbc.weightx = 1.0;
// Search Panel (Name and Barcode)
JPanel searchPanel = new JPanel(new GridBagLayout());
searchPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Tìm kiếm thuốc",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
));
mainGbc.gridx = 0; mainGbc.gridy = 0;
mainInputPanel.add(searchPanel, mainGbc);
JPanel medicineInfoPanel = new JPanel(new GridBagLayout());
medicineInfoPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Thông tin thuốc",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
));
mainGbc.gridy = 1;
mainInputPanel.add(medicineInfoPanel, mainGbc);
JPanel quantityPricePanel = new JPanel(new GridBagLayout());
quantityPricePanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Số lượng & Giá",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
));
mainGbc.gridy = 2;
mainInputPanel.add(quantityPricePanel, mainGbc);
JPanel dosagePanel = new JPanel(new GridBagLayout());
dosagePanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Liều dùng (nếu có)", // Dosage might be optional for direct sale
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
));
mainGbc.gridy = 3;
mainInputPanel.add(dosagePanel, mainGbc);
JPanel notePanel = new JPanel(new GridBagLayout());
notePanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), "Ghi chú (nếu có)",
javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP,
new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50)
));
mainGbc.gridy = 4;
mainInputPanel.add(notePanel, mainGbc);
JPanel addMedicineButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
JButton addMedicineButton = new JButton("Thêm thuốc vào HĐ");
addMedicineButtonPanel.add(addMedicineButton);
mainGbc.gridy = 5;
mainInputPanel.add(addMedicineButtonPanel, mainGbc);
mainGbc.gridy = 6; mainGbc.weighty = 1.0;
mainInputPanel.add(new JPanel(), mainGbc); // Filler panel
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(3, 5, 3, 5); // Reduced insets slightly
gbc.weightx = 0.0; gbc.weighty = 0.0;
// Search Panel Content
gbc.gridy = 0; gbc.gridx = 0;
searchPanel.add(new JLabel("Tên thuốc:"), gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
medicineNameField = new JTextField(20);
searchPanel.add(medicineNameField, gbc);
gbc.weightx = 0.0;
gbc.gridx = 2; gbc.insets = new Insets(3, 15, 3, 5); // More left inset for barcode label
searchPanel.add(new JLabel("Barcode:"), gbc);
gbc.insets = new Insets(3, 5, 3, 5);
gbc.gridx = 3; gbc.weightx = 1.0;
barcodeField = new JTextField(15);
searchPanel.add(barcodeField, gbc);
gbc.weightx = 0.0;
// Medicine Info Panel Content
gbc.gridy = 0; gbc.gridx = 0;
medicineInfoPanel.add(new JLabel("Công ty:"), gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
medicineCompanyField = new JTextField(20);
medicineCompanyField.setEditable(false);
medicineInfoPanel.add(medicineCompanyField, gbc);
gbc.weightx = 0.0;
gbc.gridy++; gbc.gridx = 0;
gbc.anchor = GridBagConstraints.NORTHWEST;
medicineInfoPanel.add(new JLabel("Mô tả:"), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 1; gbc.weightx = 1.0;
medicineDescriptionField = new JTextArea(2, 20); // Reduced rows
medicineDescriptionField.setLineWrap(true);
medicineDescriptionField.setWrapStyleWord(true);
medicineDescriptionField.setEditable(false);
JScrollPane descriptionScrollPane = new JScrollPane(medicineDescriptionField);
descriptionScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
medicineInfoPanel.add(descriptionScrollPane, gbc);
gbc.weightx = 0.0;
// Quantity & Price Panel Content (similar to MedicineDialog)
gbc.gridy = 0; gbc.gridx = 0;
quantityPricePanel.add(new JLabel("Số lượng:"), gbc);
gbc.gridx = 1; gbc.weightx = 0.5;
quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 1000, 1)); // Increased max quantity
quantitySpinner.setPreferredSize(new Dimension(80, 25));
quantityPricePanel.add(quantitySpinner, gbc);
gbc.gridx = 2; gbc.weightx = 0.0;
quantityPricePanel.add(new JLabel("Còn lại:"), gbc);
gbc.gridx = 3; gbc.weightx = 0.5;
quantityLeftField = new JTextField(5);
quantityLeftField.setEditable(false);
quantityPricePanel.add(quantityLeftField, gbc);
gbc.weightx = 0.0;
gbc.gridy++; gbc.gridx = 0;
quantityPricePanel.add(new JLabel("ĐVT:"), gbc);
gbc.gridx = 1; gbc.weightx = 0.5;
String[] units = {"Viên", "Vỉ", "Hộp", "Chai", "Tuýp", "Gói"};
UnitComboBox = new JComboBox<>(units);
UnitComboBox.setEnabled(false);
quantityPricePanel.add(UnitComboBox, gbc);
gbc.gridx = 2; gbc.weightx = 0.0;
quantityPricePanel.add(new JLabel("Giá (VNĐ):"), gbc);
gbc.gridx = 3; gbc.weightx = 0.5;
priceField = new JTextField(10);
priceField.setEditable(false);
quantityPricePanel.add(priceField, gbc);
gbc.weightx = 0.0;
gbc.gridy++; gbc.gridx = 0;
quantityPricePanel.add(new JLabel("Thành tiền (VNĐ):"), gbc);
gbc.gridx = 1; gbc.gridwidth = 3; gbc.weightx = 1.0;
totalField = new JTextField(10);
totalField.setEditable(false);
quantityPricePanel.add(totalField, gbc);
gbc.gridwidth = 1; gbc.weightx = 0.0;
// Dosage Panel Content
gbc.gridy = 0; gbc.gridx = 0; gbc.weightx = 0.0;
dosagePanel.add(new JLabel("Sáng:"), gbc);
gbc.gridx = 1; gbc.weightx = 0.3;
morningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
morningSpinner.setPreferredSize(new Dimension(60,25));
dosagePanel.add(morningSpinner, gbc);
gbc.gridx = 2; gbc.weightx = 0.0;
dosagePanel.add(new JLabel("Trưa:"), gbc);
gbc.gridx = 3; gbc.weightx = 0.3;
noonSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
noonSpinner.setPreferredSize(new Dimension(60,25));
dosagePanel.add(noonSpinner, gbc);
gbc.gridx = 4; gbc.weightx = 0.0;
dosagePanel.add(new JLabel("Chiều:"), gbc);
gbc.gridx = 5; gbc.weightx = 0.3;
eveningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
eveningSpinner.setPreferredSize(new Dimension(60,25));
dosagePanel.add(eveningSpinner, gbc);
gbc.weightx = 0.0;
// Note Panel Content
gbc.gridy = 0; gbc.gridx = 0;
notePanel.add(new JLabel("Ghi chú:"), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 1; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.weighty = 1.0;
noteField = new JTextArea(2, 20); // Reduced rows
noteField.setLineWrap(true);
noteField.setWrapStyleWord(true);
JScrollPane noteScrollPane = new JScrollPane(noteField);
noteScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
notePanel.add(noteScrollPane, gbc);
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.0; gbc.weighty = 0.0;
// Medicine Table (Right Side)
tableModel = new DefaultTableModel(medicineData, medcineColumns) {
@Override public boolean isCellEditable(int row, int column) { return false; }
};
medicineTable = new JTable(tableModel);
medicineTable.setFont(new Font("Arial", Font.PLAIN, 13));
medicineTable.setRowHeight(22);
medicineTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
medicineTable.setAutoCreateRowSorter(true);
JScrollPane medicineTableScrollPane = new JScrollPane(medicineTable);
medicineTableScrollPane.setPreferredSize(new Dimension(650, 250)); // Wider table
// Selected Medicine Table (Right Side, Bottom)
String[] selectedColumnNames = {"ID", "Tên thuốc", "SL", "ĐVT", "Sáng", "Trưa", "Chiều", "Đơn giá", "Thành tiền", "Ghi chú"};
selectedTableModel = new DefaultTableModel(new String[][]{}, selectedColumnNames) {
@Override public boolean isCellEditable(int row, int column) {
// Allow editing for SL, Dosage, Note in selected table
return column == 2 || column == 4 || column == 5 || column == 6 || column == 9;
}
};
selectedTable = new JTable(selectedTableModel);
selectedTable.setFont(new Font("Arial", Font.PLAIN, 13));
selectedTable.setRowHeight(22);
JScrollPane selectedTableScrollPane = new JScrollPane(selectedTable);
selectedTableScrollPane.setPreferredSize(new Dimension(650, 180)); // Wider table
JPanel removeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton removeMedicineButton = new JButton("Xóa thuốc đã chọn");
removeButtonPanel.add(removeMedicineButton);
JPanel rightPanel = new JPanel(new BorderLayout(0, 5));
rightPanel.add(medicineTableScrollPane, BorderLayout.CENTER);
JPanel bottomRightsPanel = new JPanel(new BorderLayout(0,5));
bottomRightsPanel.add(selectedTableScrollPane, BorderLayout.CENTER);
bottomRightsPanel.add(removeButtonPanel, BorderLayout.SOUTH);
rightPanel.add(bottomRightsPanel, BorderLayout.SOUTH);
JScrollPane mainInputScrollPane = new JScrollPane(mainInputPanel);
mainInputScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
mainInputScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
mainInputScrollPane.setBorder(BorderFactory.createEmptyBorder());
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mainInputScrollPane, rightPanel);
splitPane.setResizeWeight(0.42); // Adjusted resize weight slightly
add(splitPane, BorderLayout.CENTER);
JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton okButton = new JButton("Lưu & Đóng");
JButton cancelButton = new JButton("Hủy");
bottomButtonPanel.add(okButton);
bottomButtonPanel.add(cancelButton);
add(bottomButtonPanel, BorderLayout.SOUTH);
// Event Listeners
medicineNameField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); }
public void removeUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); }
public void insertUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); }
});
barcodeField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
filterMedicineTableByBarcode();
}
}
});
medicineTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
SwingUtilities.invokeLater(() -> {
int selectedRow = medicineTable.getSelectedRow();
if (selectedRow != -1) handleMedicineTableRowSelection(selectedRow);
});
}
}
});
medicineNameField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
handleTableNavigationKeys(e, medicineTable);
}
});
barcodeField.addKeyListener(new KeyAdapter() { // Allow table nav from barcode field too
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
filterMedicineTableByBarcode();
} else {
handleTableNavigationKeys(e, medicineTable);
}
}
});
quantitySpinner.addChangeListener(e -> updateTotalField());
UnitComboBox.addActionListener(e -> updateTotalField()); // Though disabled, good practice
addMedicineButton.addActionListener(e -> addSelectedMedicine());
removeMedicineButton.addActionListener(e -> removeSelectedMedicine());
okButton.addActionListener(e -> {
collectPrescriptionData();
if (medicinePrescription == null || medicinePrescription.length == 0) {
// For standalone sale, it's okay to have no prescription if just closing
// But if they intend to add items, perhaps a different message or behavior
}
logger.info("Standalone prescription/items saved.");
// Log for debugging
for (String[] row : medicinePrescription) {
logger.info("Med: ID={}, Name={}, Qty={}, Unit={}, M={}, N={}, E={}, Price={}, Total={}, Note={}",
(Object[]) row);
}
dispose();
});
cancelButton.addActionListener(e -> {
medicinePrescription = null; // Clear any collected data if cancelled
dispose();
});
}
private void handleTableNavigationKeys(KeyEvent e, JTable targetTable) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (targetTable.getSelectedRow() != -1) addSelectedMedicine();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
if (targetTable.getRowCount() > 0) {
int selectedRow = targetTable.getSelectedRow();
selectedRow = (selectedRow < targetTable.getRowCount() - 1) ? selectedRow + 1 : 0;
targetTable.setRowSelectionInterval(selectedRow, selectedRow);
targetTable.scrollRectToVisible(targetTable.getCellRect(selectedRow, 0, true));
handleMedicineTableRowSelection(selectedRow);
}
} else if (e.getKeyCode() == KeyEvent.VK_UP) {
if (targetTable.getRowCount() > 0) {
int selectedRow = targetTable.getSelectedRow();
selectedRow = (selectedRow > 0) ? selectedRow - 1 : targetTable.getRowCount() - 1;
targetTable.setRowSelectionInterval(selectedRow, selectedRow);
targetTable.scrollRectToVisible(targetTable.getCellRect(selectedRow, 0, true));
handleMedicineTableRowSelection(selectedRow);
}
}
}
private void filterMedicineTableByName() {
filterMedicineTable(medicineNameField.getText().trim(), null);
}
private void filterMedicineTableByBarcode() {
filterMedicineTable(null, barcodeField.getText().trim());
if (medicineTable.getRowCount() == 1) { // If unique match by barcode
medicineTable.setRowSelectionInterval(0,0);
handleMedicineTableRowSelection(0);
addSelectedMedicine(); // Optionally auto-add if unique and quantity is set
barcodeField.setText(""); // Clear barcode field after processing
medicineNameField.requestFocusInWindow(); // Or quantitySpinner
}
}
private void filterMedicineTable(String nameFilter, String barcodeFilter) {
if (medicineData == null) return;
boolean nameIsEmpty = (nameFilter == null || nameFilter.isEmpty());
boolean barcodeIsEmpty = (barcodeFilter == null || barcodeFilter.isEmpty());
if (nameIsEmpty && barcodeIsEmpty) {
tableModel.setDataVector(medicineData, medcineColumns);
resizeMedicineTableColumns();
medicineTable.clearSelection();
clearInputFields(); // Also clear input fields when filter is cleared
} else {
List<String[]> filteredData = new ArrayList<>();
String lowerCaseNameFilter = nameIsEmpty ? "" : TextUtils.removeAccents(nameFilter.toLowerCase());
// Barcode is usually exact match or startsWith, not contains typically
String lowerCaseBarcodeFilter = barcodeIsEmpty ? "" : barcodeFilter.toLowerCase();
for (String[] row : medicineData) {
boolean nameMatch = nameIsEmpty; // True if no name filter
boolean barcodeMatch = barcodeIsEmpty; // True if no barcode filter
if (!nameIsEmpty && row.length > 1 && row[1] != null) {
nameMatch = TextUtils.removeAccents(row[1].toLowerCase()).contains(lowerCaseNameFilter);
}
// Assuming barcode is at index 2 in medicineData from server (ID, Name, Barcode, Company...)
if (!barcodeIsEmpty && row.length > 2 && row[2] != null) {
barcodeMatch = row[2].toLowerCase().equals(lowerCaseBarcodeFilter); // Exact match for barcode
}
if (nameMatch && barcodeMatch) { // If name filter provided, must match name. If barcode provided, must match barcode.
filteredData.add(row);
}
}
tableModel.setDataVector(filteredData.toArray(new String[0][0]), medcineColumns);
resizeMedicineTableColumns();
if (!filteredData.isEmpty()) {
medicineTable.setRowSelectionInterval(0,0);
handleMedicineTableRowSelection(0);
} else {
clearInputFields();
}
}
}
private void resizeMedicineTableColumns() {
if (medicineTable.getColumnCount() == 0) return;
columnModel = medicineTable.getColumnModel();
// ID, Tên thuốc, Barcode, Công ty, Mô tả, Tồn kho, ĐVT, Giá
columnModel.getColumn(0).setPreferredWidth(30); // ID
columnModel.getColumn(1).setPreferredWidth(150); // Tên thuốc
columnModel.getColumn(2).setPreferredWidth(100); // Barcode
columnModel.getColumn(3).setPreferredWidth(100); // Công ty
columnModel.getColumn(4).setPreferredWidth(180); // Mô tả
columnModel.getColumn(5).setPreferredWidth(50); // Tồn kho
columnModel.getColumn(6).setPreferredWidth(40); // ĐVT
columnModel.getColumn(7).setPreferredWidth(60); // Giá
}
private void handleMedicineTableRowSelection(int viewRow) {
if (viewRow < 0 || viewRow >= medicineTable.getRowCount()) {
clearInputFields();
return;
}
int modelRow = medicineTable.convertRowIndexToModel(viewRow);
isProgrammaticallySettingMedicineNameField = true;
medicineNameField.setText(tableModel.getValueAt(modelRow, 1).toString());
isProgrammaticallySettingMedicineNameField = false;
// Assuming barcode is column 2. If not present in tableModel directly, might need to fetch from medicineData using ID
if (tableModel.getColumnCount() > 2 && tableModel.getValueAt(modelRow, 2) != null) {
barcodeField.setText(tableModel.getValueAt(modelRow, 2).toString());
}
medicineCompanyField.setText(tableModel.getValueAt(modelRow, 3).toString());
medicineDescriptionField.setText(tableModel.getValueAt(modelRow, 4).toString());
quantityLeftField.setText(tableModel.getValueAt(modelRow, 5).toString());
UnitComboBox.setSelectedItem(tableModel.getValueAt(modelRow, 6).toString());
priceField.setText(tableModel.getValueAt(modelRow, 7).toString());
quantitySpinner.setValue(1);
morningSpinner.setValue(0); // Reset dosage for standalone sale, or make it optional
noonSpinner.setValue(0);
eveningSpinner.setValue(0);
noteField.setText("");
updateTotalField();
}
private void clearInputFields() {
// Don't clear medicineNameField or barcodeField as they are used for filtering
medicineCompanyField.setText("");
medicineDescriptionField.setText("");
quantityLeftField.setText("");
UnitComboBox.setSelectedIndex(0); // Or set to a default/blank if applicable
priceField.setText("");
totalField.setText("");
quantitySpinner.setValue(1);
morningSpinner.setValue(0);
noonSpinner.setValue(0);
eveningSpinner.setValue(0);
noteField.setText("");
}
private void updateTotalField() {
try {
int quantity = (Integer) quantitySpinner.getValue();
if (quantity < 0) { quantitySpinner.setValue(0); quantity = 0; }
double price = priceField.getText().isEmpty() ? 0 : Double.parseDouble(priceField.getText().replace(",",""));
totalField.setText(String.format("%,.0f", quantity * price)); // Format with commas
} catch (NumberFormatException e) {
totalField.setText("Giá trị không hợp lệ");
logger.error("Error parsing price for total calculation: " + priceField.getText(), e);
}
}
private void addSelectedMedicine() {
int selectedRowInMedicineTable = medicineTable.getSelectedRow();
String medicineId, name, unit, currentPrice, barcodeVal;
if (selectedRowInMedicineTable != -1) {
int modelRow = medicineTable.convertRowIndexToModel(selectedRowInMedicineTable);
medicineId = tableModel.getValueAt(modelRow, 0).toString();
name = tableModel.getValueAt(modelRow, 1).toString();
barcodeVal = tableModel.getColumnCount() > 2 && tableModel.getValueAt(modelRow, 2) != null ? tableModel.getValueAt(modelRow, 2).toString() : "N/A";
unit = tableModel.getValueAt(modelRow, 6).toString(); // ĐVT is at 6 (0-indexed)
currentPrice = tableModel.getValueAt(modelRow, 7).toString(); // Giá is at 7
} else if (!medicineNameField.getText().isEmpty() || !barcodeField.getText().isEmpty()){
// Attempt to find by name or barcode if no table selection but fields have text
String searchName = medicineNameField.getText().trim();
String searchBarcode = barcodeField.getText().trim();
String foundId = null, foundUnit = null, foundPrice = null, actualName = null, actualBarcode = null;
for (String[] med : medicineData) { // medicineData: ID, Tên thuốc, Barcode, Công ty, Mô tả, Tồn kho, ĐVT, Giá
boolean nameMatches = !searchName.isEmpty() && TextUtils.removeAccents(med[1].toLowerCase()).contains(TextUtils.removeAccents(searchName.toLowerCase()));
boolean barcodeMatches = !searchBarcode.isEmpty() && med.length > 2 && med[2] != null && med[2].equalsIgnoreCase(searchBarcode);
if (nameMatches || barcodeMatches) {
foundId = med[0]; actualName = med[1]; actualBarcode = med[2];
foundUnit = med[6]; foundPrice = med[7];
break; // Take first match
}
}
if (foundId == null) {
JOptionPane.showMessageDialog(this, "Không tìm thấy thuốc khớp với tên hoặc barcode đã nhập.", "Không tìm thấy thuốc", JOptionPane.WARNING_MESSAGE);
return;
}
medicineId = foundId; name = actualName; unit = foundUnit; currentPrice = foundPrice; barcodeVal = actualBarcode;
} else {
JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ danh sách hoặc nhập tên/barcode.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE);
return;
}
int quantity = (Integer) quantitySpinner.getValue();
int morningVal = (Integer) morningSpinner.getValue();
int noonVal = (Integer) noonSpinner.getValue();
int eveningVal = (Integer) eveningSpinner.getValue();
String note = noteField.getText();
String totalAmount = totalField.getText().replace(",",""); // Remove comma for internal storage if needed
if (quantity <= 0) {
JOptionPane.showMessageDialog(this, "Số lượng phải lớn hơn 0.", "Số lượng không hợp lệ", JOptionPane.WARNING_MESSAGE);
return;
}
selectedTableModel.addRow(new Object[]{medicineId, name, quantity, unit, morningVal, noonVal, eveningVal, currentPrice, totalAmount, note});
}
private void removeSelectedMedicine() {
int selectedRow = selectedTable.getSelectedRow();
if (selectedRow != -1) {
selectedTableModel.removeRow(selectedRow);
} else {
JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ bảng dưới để xóa.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE);
}
}
private void collectPrescriptionData() {
int rowCount = selectedTableModel.getRowCount();
if (rowCount == 0) {
medicinePrescription = new String[0][0];
return;
}
medicinePrescription = new String[rowCount][10];
for (int i = 0; i < rowCount; i++) {
for (int j = 0; j < 10; j++) {
medicinePrescription[i][j] = selectedTableModel.getValueAt(i, j) != null ? selectedTableModel.getValueAt(i, j).toString() : "";
}
}
}
@Override
public void dispose() {
logger.info("Cleaning up StandaloneMedicineDialog listeners");
ClientHandler.deleteListener(GetMedInfoResponse.class); // Ensure this matches the listener added
super.dispose();
}
// Main method for testing this dialog independently
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Test Standalone Medicine Dialog");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
JButton openDialogButton = new JButton("Open Standalone Medicine Dialog");
openDialogButton.addActionListener(e -> {
StandaloneMedicineDialog dialog = new StandaloneMedicineDialog(frame);
dialog.setVisible(true);
String[][] prescription = dialog.getMedicinePrescription();
if (prescription != null && prescription.length > 0) {
System.out.println("Standalone Items obtained:");
for (String[] item : prescription) {
// Ensure correct number of items for format string
if (item.length == 10) {
System.out.println(String.format("ID:%s, Name:%s, Qty:%s, Unit:%s, M:%s, N:%s, E:%s, Price:%s, Total:%s, Note:%s",
(Object[]) item));
} else {
System.out.println("Item with incorrect number of fields: " + java.util.Arrays.toString(item));
}
}
} else {
System.out.println("No items or dialog was cancelled.");
}
});
frame.setLayout(new FlowLayout());
frame.add(openDialogButton);
frame.setVisible(true);
});
}
}
@@ -26,6 +26,8 @@ public class Patient {
private String checkupType;
private String conclusion;
private String reCheckupDate;
private String cccdDdcn;
public Patient(String[][] array) {
this.checkupId = array[0][0];
@@ -47,6 +49,9 @@ public class Patient {
this.checkupType = array[0][16];
this.conclusion = array[0][17];
this.reCheckupDate = array[0][18];
if (array[0].length > 19) {
this.cccdDdcn = array[0][19];
}
}
// New constructor for a single patient data row
@@ -73,5 +78,8 @@ public class Patient {
this.checkupType = data[16];
this.conclusion = data[17];
this.reCheckupDate = data[18];
if (data.length > 19) {
this.cccdDdcn = data[19];
}
}
}
@@ -15,4 +15,5 @@ public class AddPatientRequest implements Packet {
String patientPhone;
String patientAddress;
String patientGender;
String patientCccdDdcn;
}
@@ -5,29 +5,35 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode//(callSuper = true) // Cannot call super on an interface implementation
@Data
@AllArgsConstructor
public class SaveCheckupRequest implements Packet {
private String checkupId;
private String checkupDate; // Consider sending as long (timestamp) if consistency is needed
private String customerId;
private String customerLastName;
private String customerFirstName;
private String customerDob; // Consider sending as long (timestamp)
private String customerGender;
private String customerAddress; // Full address string: "Street, Ward, District, Province"
private String customerNumber; // Phone number
private String customerWeight;
private String customerHeight;
private String doctorId; // Or doctorId if preferred for backend processing
private String suggestions;
private String diagnosis;
private String notes;
private String status;
private String checkupType;
private String conclusion;
private String reCheckupDate;
private String[][] medicinePrescription; // Structure: [med_id, name, quantity, unit, dose_morning, dose_noon, dose_evening, unit_price, total_price, notes]
private String[][] servicePrescription; // Structure: [ser_id, name, quantity, unit_price, total_price, notes]
String checkupId;
String customerId;
String doctorId;
String checkupDate;
// Checkup Details
String suggestions;
String diagnosis;
String notes;
String status;
String checkupType;
String conclusion;
String reCheckupDate;
// Patient Details
String customerFirstName;
String customerLastName;
String customerDob;
String customerGender;
String customerAddress;
String customerNumber;
String customerWeight;
String customerHeight;
String customerCccdDdcn;
// Prescriptions
String[][] medicinePrescription;
String[][] servicePrescription;
}
@@ -110,7 +110,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
ResultSet rs = statement.executeQuery(
"select a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name,\n" +
"d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, a.status, a.customer_id, \n" +
"c.customer_number, c.customer_address, c.customer_weight, c.customer_height, c.customer_gender, c.customer_dob, a.checkup_type, a.conclusion, a.reCheckupDate\n" +
"c.customer_number, c.customer_address, c.customer_weight, c.customer_height, c.customer_gender, c.customer_dob, a.checkup_type, a.conclusion, a.reCheckupDate, c.cccd_ddcn\n" +
"from checkup as a\n" +
"join customer as c on a.customer_id = c.customer_id\n" +
"join Doctor D on a.doctor_id = D.doctor_id\n" +
@@ -148,11 +148,12 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String checkupType = rs.getString("checkup_type");
String conclusion = rs.getString("conclusion");
String reCheckupDate = rs.getString("reCheckupDate");
String cccdDdcn = rs.getString("cccd_ddcn");
String result = String.join("|", checkupId,
checkupDate, customerLastName, customerFirstName,
doctorLastName + " " + doctorFirstName, suggestion,
diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight,
customerGender, customerDob, checkupType, conclusion, reCheckupDate
customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn
);
resultList.add(result);
@@ -177,7 +178,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
ResultSet rs = statement.executeQuery(
"select a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name,\n" +
"d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, a.status, a.customer_id, \n" +
"c.customer_number, c.customer_address, c.customer_weight, c.customer_height, c.customer_gender, c.customer_dob, a.checkup_type, a.conclusion, a.reCheckupDate\n" +
"c.customer_number, c.customer_address, c.customer_weight, c.customer_height, c.customer_gender, c.customer_dob, a.checkup_type, a.conclusion, a.reCheckupDate, c.cccd_ddcn\n" +
"from checkup as a\n" +
"join customer as c on a.customer_id = c.customer_id\n" +
"join Doctor D on a.doctor_id = D.doctor_id\n" +
@@ -219,12 +220,13 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String checkupType = rs.getString("checkup_type");
String conclusion = rs.getString("conclusion");
String reCheckupDate = rs.getString("reCheckupDate");
String cccdDdcn = rs.getString("cccd_ddcn");
String result = String.join("|", checkupId,
checkupDate, customerLastName, customerFirstName,
doctorLastName + " " + doctorFirstName, suggestion,
diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight,
customerGender, customerDob, checkupType, conclusion, reCheckupDate
customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn
);
resultList.add(result);
@@ -446,7 +448,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
try {
ResultSet rs = statement.executeQuery(
"SELECT customer_id, customer_last_name, customer_first_name, customer_dob, customer_number, " +
"customer_address, customer_address, customer_gender\n" +
"customer_address, customer_address, customer_gender, cccd_ddcn\n" +
"FROM Customer\n" +
"ORDER BY customer_id DESC\n" +
"LIMIT 20;");
@@ -463,9 +465,10 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String customerNumber = rs.getString("customer_number");
String customerAddress = rs.getString("customer_address");
String customerGender = rs.getString("customer_gender");
String cccdDdcn = rs.getString("cccd_ddcn");
String result = String.join("|", customerId, customerLastName + " " + customerFirstName,
year, customerNumber, customerAddress, customerGender, customerDob);
year, customerNumber, customerAddress, customerGender, customerDob, cccdDdcn);
resultList.add(result);
}
@@ -594,7 +597,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
Server.connection.setAutoCommit(false);
PreparedStatement preparedStatement = Server.connection.prepareStatement(
"INSERT INTO Customer (customer_last_name, customer_first_name, customer_dob, customer_number, customer_address, customer_gender) VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO Customer (customer_last_name, customer_first_name, customer_dob, customer_number, customer_address, customer_gender, cccd_ddcn) VALUES (?, ?, ?, ?, ?, ?, ?)",
PreparedStatement.RETURN_GENERATED_KEYS // Use this instead of separate query
);
preparedStatement.setString(1, addPatientRequest.getPatientLastName());
@@ -603,6 +606,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
preparedStatement.setString(4, addPatientRequest.getPatientPhone());
preparedStatement.setString(5, addPatientRequest.getPatientAddress());
preparedStatement.setString(6, addPatientRequest.getPatientGender());
preparedStatement.setString(7, addPatientRequest.getPatientCccdDdcn());
preparedStatement.executeUpdate();
// Get the generated key safely
@@ -722,8 +726,8 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
INSERT INTO Customer (
customer_id, customer_first_name, customer_last_name, customer_dob,
customer_gender, customer_address, customer_number,
customer_weight, customer_height
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
customer_weight, customer_height, cccd_ddcn
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(customer_id) DO UPDATE SET
customer_first_name = excluded.customer_first_name,
customer_last_name = excluded.customer_last_name,
@@ -732,7 +736,8 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
customer_address = excluded.customer_address,
customer_number = excluded.customer_number,
customer_weight = excluded.customer_weight,
customer_height = excluded.customer_height
customer_height = excluded.customer_height,
cccd_ddcn = excluded.cccd_ddcn
""";
PreparedStatement customerStmt = conn.prepareStatement(customerSql);
@@ -745,6 +750,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
customerStmt.setString(7, saveCheckupRequest.getCustomerNumber());
customerStmt.setString(8, saveCheckupRequest.getCustomerWeight());
customerStmt.setString(9, saveCheckupRequest.getCustomerHeight());
customerStmt.setString(10, saveCheckupRequest.getCustomerCccdDdcn());
customerStmt.executeUpdate();
log.info("Customer saved successfully");
// 2. Generate prescription_id if we have medicine prescriptions
Binary file not shown.