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 282c379..970f911 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 @@ -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(", "); 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 8ffd7f3..436613a 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -142,11 +142,11 @@ public class CheckUpPage extends JPanel { private final ResponseListener orderInfoByCheckupListener = this::handleGetOrderInfoByCheckupResponse; private final ResponseListener getAllTemplatesListener = this::handleGetAllTemplatesResponse; private final ResponseListener 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 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); diff --git a/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java b/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java index 55289bd..0b56e1c 100644 --- a/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java +++ b/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java @@ -2,7 +2,6 @@ package BsK.client.ui.component.CheckoutPage; import BsK.client.LocalStorage; import BsK.client.ui.component.MainFrame; -import BsK.client.ui.component.CheckoutPage.StandaloneMedicineDialog; import BsK.client.ui.component.common.NavBar; import BsK.client.ui.component.common.RoundedPanel; import lombok.extern.slf4j.Slf4j; @@ -10,353 +9,1077 @@ import lombok.extern.slf4j.Slf4j; import javax.swing.*; import javax.swing.border.Border; import javax.swing.table.DefaultTableModel; +import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Date; -import java.awt.event.ActionListener; +import java.util.Locale; @Slf4j public class CheckoutPage extends JPanel { private MainFrame mainFrame; - - // Left Panel Components - private JTable finishedCheckupTable; - private DefaultTableModel finishedCheckupTableModel; - private JButton newStandaloneBillButton; - - // Right Panel Components (Bill Details) - private JLabel billIdLabel; - private JLabel billDateLabel; - private JTextField customerNameField; // For standalone bills - private JTextField customerPhoneField; // For standalone bills - private JTextField barcodeField; - private JButton scanBarcodeButton; - private JButton addMedicineItemButton; - private JButton removeMedicineItemButton; - private JTable billItemsTable; - private DefaultTableModel billItemsTableModel; - private JLabel subtotalAmountLabel; - private JTextField discountField; - private JLabel grandTotalAmountLabel; - private JButton processPaymentButton; - private JButton printBillButton; - private StandaloneMedicineDialog standaloneMedicineDialog; - + // Top Panel Components + private JTextField searchPatientField; + private JLabel patientInfoLabel; + + // Left Panel - Prescription Checklist (TO-DO) + private JTable prescriptionChecklistTable; + private DefaultTableModel prescriptionChecklistModel; + private JTextArea doctorNotesArea; + + // Middle Panel - Dispensed Items (DONE) + private JTable dispensedItemsTable; + private DefaultTableModel dispensedItemsModel; + + // Right Panel - Payment Summary + private JLabel medicineSubtotalLabel; + private JLabel serviceSubtotalLabel; + private JLabel grandTotalLabel; + private JTextField cashReceivedField; + private JLabel changeLabel; + private JButton cancelButton; + private JButton payAndPrintButton; + + // Dispensing workflow components + private JDialog configureItemDialog; + private JComboBox unitComboBox; + private JLabel unitPriceLabel; + private JSpinner quantitySpinner; + private JLabel lineTotalLabel; + private JTable batchTable; + private DefaultTableModel batchTableModel; + private JButton confirmAddButton; + private JButton cancelDialogButton; + + // Current dispensing data + private String currentDispensingDrug = ""; + private double currentUnitPrice = 0.0; + + // Current patient data + private String currentPatientId = ""; + private String currentPatientName = ""; + private String currentDoctorName = ""; + private double medicineTotal = 0.0; + private double serviceTotal = 0.0; + + // Number formatter for currency + private NumberFormat currencyFormatter; public CheckoutPage(MainFrame mainFrame) { this.mainFrame = mainFrame; + this.currencyFormatter = NumberFormat.getNumberInstance(new Locale("vi", "VN")); + setLayout(new BorderLayout()); - setBackground(new Color(240, 240, 240)); + setBackground(new Color(245, 245, 245)); // --- Navigation Bar --- - NavBar navBar = new NavBar(mainFrame, "Thanh toán"); + NavBar navBar = new NavBar(mainFrame, "Thanh Toán & Cấp Phát Thuốc"); add(navBar, BorderLayout.NORTH); - // --- Main Content Area --- - JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); - mainSplitPane.setResizeWeight(0.35); // Give more space to bill details initially - mainSplitPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + initializeComponents(); + layoutComponents(); + setupEventListeners(); + setupKeyboardShortcuts(); + + // Focus on search field by default + SwingUtilities.invokeLater(() -> searchPatientField.requestFocusInWindow()); + } - // --- Left Panel: Queues and New Bill --- - JPanel leftPanel = new JPanel(new BorderLayout(10, 10)); - leftPanel.setBorder(BorderFactory.createEmptyBorder(10,0,0,0)); - - // Finished Checkup Queue Panel - RoundedPanel finishedQueuePanel = new RoundedPanel(15, Color.WHITE, false); - finishedQueuePanel.setLayout(new BorderLayout()); - finishedQueuePanel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createLineBorder(new Color(63, 81, 181), 1, true), - "Chờ thanh toán (đã khám)", - javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 16), new Color(63, 81, 181) + private void initializeComponents() { + // Top Panel Components + searchPatientField = new JTextField(35); + searchPatientField.setFont(new Font("Arial", Font.PLAIN, 18)); // Increased font size + searchPatientField.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(63, 81, 181), 2), + BorderFactory.createEmptyBorder(10, 15, 10, 15) )); - String[] finishedColumns = {"Mã Bill", "Tên BN", "Tổng tiền", "Trạng thái"}; - finishedCheckupTableModel = new DefaultTableModel(new Object[][]{}, finishedColumns) { - public boolean isCellEditable(int row, int column) { return false; } + patientInfoLabel = new JLabel("Chưa chọn bệnh nhân"); + patientInfoLabel.setFont(new Font("Arial", Font.BOLD, 16)); // Increased font size + patientInfoLabel.setForeground(new Color(63, 81, 181)); + + // Left Panel - Prescription Checklist (NO SELECT ALL CHECKBOX) + String[] prescriptionColumns = {"✓", "Tên thuốc/Dịch vụ", "SL Cần", "Thao tác"}; + prescriptionChecklistModel = new DefaultTableModel(prescriptionColumns, 0) { + @Override + public Class getColumnClass(int column) { + if (column == 0) return Boolean.class; + if (column == 3) return JButton.class; // Button column + return String.class; + } + @Override + public boolean isCellEditable(int row, int column) { + return column == 0 || column == 3; // Checkbox and button are editable + } }; - finishedCheckupTable = new JTable(finishedCheckupTableModel); - finishedCheckupTable.setRowHeight(25); - finishedCheckupTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); - finishedCheckupTable.setFont(new Font("Arial", Font.PLAIN, 12)); - JScrollPane finishedTableScroll = new JScrollPane(finishedCheckupTable); - finishedQueuePanel.add(finishedTableScroll, BorderLayout.CENTER); - // TODO: Populate this table with patients who have Status.COMPLETED_CHECKUP or similar - // TODO: Add MouseListener to finishedCheckupTable to load bill details on selection + prescriptionChecklistTable = new JTable(prescriptionChecklistModel); + setupPrescriptionTable(); - leftPanel.add(finishedQueuePanel, BorderLayout.CENTER); - - newStandaloneBillButton = new JButton("Tạo hóa đơn mới (khách lẻ)"); - newStandaloneBillButton.setFont(new Font("Arial", Font.BOLD, 14)); - newStandaloneBillButton.setBackground(new Color(0, 150, 136)); - newStandaloneBillButton.setForeground(Color.WHITE); - newStandaloneBillButton.setFocusPainted(false); - newStandaloneBillButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - newStandaloneBillButton.setBorder(BorderFactory.createEmptyBorder(10,15,10,15)); - newStandaloneBillButton.addActionListener(e -> createNewStandaloneBill()); - JPanel newBillButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - newBillButtonPanel.setOpaque(false); - newBillButtonPanel.add(newStandaloneBillButton); - leftPanel.add(newBillButtonPanel, BorderLayout.SOUTH); - - mainSplitPane.setLeftComponent(leftPanel); - - // --- Right Panel: Bill Details --- - RoundedPanel rightPanel = new RoundedPanel(15, Color.WHITE, false); - rightPanel.setLayout(new BorderLayout(10,10)); - rightPanel.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(0, 10, 0, 0), - BorderFactory.createTitledBorder( - BorderFactory.createLineBorder(new Color(63, 81, 181), 1, true), - "Chi tiết hóa đơn", - javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 16), new Color(63, 81, 181) - ) + doctorNotesArea = new JTextArea(3, 20); + doctorNotesArea.setEditable(false); + doctorNotesArea.setBackground(new Color(250, 250, 250)); + doctorNotesArea.setFont(new Font("Arial", Font.ITALIC, 14)); // Increased font size + doctorNotesArea.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("Ghi chú của Bác sĩ"), + BorderFactory.createEmptyBorder(8, 8, 8, 8) )); + doctorNotesArea.setLineWrap(true); + doctorNotesArea.setWrapStyleWord(true); - JPanel billInfoAndActionsPanel = new JPanel(new GridBagLayout()); + // Middle Panel - Dispensed Items + String[] dispensedColumns = {"Tên thuốc/Dịch vụ", "Lô", "HSD", "SL Đã lấy", "Đơn giá", "Thành tiền"}; + dispensedItemsModel = new DefaultTableModel(dispensedColumns, 0) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }; + dispensedItemsTable = new JTable(dispensedItemsModel); + setupDispensedTable(); + + // Right Panel - Payment Summary (RIGHT-ALIGNED VALUES) + medicineSubtotalLabel = new JLabel("0 VNĐ", SwingConstants.RIGHT); + medicineSubtotalLabel.setFont(new Font("Arial", Font.PLAIN, 16)); + + serviceSubtotalLabel = new JLabel("0 VNĐ", SwingConstants.RIGHT); + serviceSubtotalLabel.setFont(new Font("Arial", Font.PLAIN, 16)); + + grandTotalLabel = new JLabel("0 VNĐ", SwingConstants.RIGHT); + grandTotalLabel.setFont(new Font("Arial", Font.BOLD, 22)); // Increased font size + grandTotalLabel.setForeground(new Color(244, 67, 54)); + + cashReceivedField = new JTextField(15); + cashReceivedField.setFont(new Font("Arial", Font.PLAIN, 18)); // Increased font size + cashReceivedField.setHorizontalAlignment(JTextField.RIGHT); + + changeLabel = new JLabel("0 VNĐ", SwingConstants.RIGHT); + changeLabel.setFont(new Font("Arial", Font.BOLD, 18)); // Increased font size + changeLabel.setForeground(new Color(76, 175, 80)); + + // DE-EMPHASIZED CANCEL BUTTON (Grey outline style) + cancelButton = new JButton("HỦY BỎ"); + cancelButton.setFont(new Font("Arial", Font.PLAIN, 14)); // Not bold + cancelButton.setBackground(Color.WHITE); + cancelButton.setForeground(new Color(120, 120, 120)); + cancelButton.setBorder(BorderFactory.createLineBorder(new Color(180, 180, 180), 1)); + cancelButton.setFocusPainted(false); + cancelButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + payAndPrintButton = new JButton("THANH TOÁN & IN HÓA ĐƠN (F9)"); + payAndPrintButton.setFont(new Font("Arial", Font.BOLD, 16)); + payAndPrintButton.setBackground(new Color(76, 175, 80)); + payAndPrintButton.setForeground(Color.WHITE); + payAndPrintButton.setFocusPainted(false); + payAndPrintButton.setPreferredSize(new Dimension(300, 55)); + payAndPrintButton.setEnabled(false); // Initially disabled + payAndPrintButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + + private void setupPrescriptionTable() { + prescriptionChecklistTable.setRowHeight(35); // Increased row height + prescriptionChecklistTable.setFont(new Font("Arial", Font.PLAIN, 15)); // Increased font size + prescriptionChecklistTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 15)); + prescriptionChecklistTable.getTableHeader().setBackground(new Color(63, 81, 181)); + prescriptionChecklistTable.getTableHeader().setForeground(Color.WHITE); + + // Set column widths - adjusted for Dispense button column + prescriptionChecklistTable.getColumnModel().getColumn(0).setPreferredWidth(40); + prescriptionChecklistTable.getColumnModel().getColumn(1).setPreferredWidth(200); + prescriptionChecklistTable.getColumnModel().getColumn(2).setPreferredWidth(70); + prescriptionChecklistTable.getColumnModel().getColumn(3).setPreferredWidth(80); // Thao tác column + + // Button renderer and editor for Dispense column (column 3) + prescriptionChecklistTable.getColumnModel().getColumn(3).setCellRenderer(new javax.swing.table.DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JButton button = new JButton("Cấp thuốc"); + button.setFont(new Font("Arial", Font.PLAIN, 11)); + button.setBackground(new Color(33, 150, 243)); + button.setForeground(Color.WHITE); + button.setFocusPainted(false); + + // Disable button if item is already checked + Boolean checked = (Boolean) table.getValueAt(row, 0); + if (checked != null && checked) { + button.setText("Đã cấp"); + button.setBackground(new Color(158, 158, 158)); + button.setEnabled(false); + } + + return button; + } + }); + + prescriptionChecklistTable.getColumnModel().getColumn(3).setCellEditor(new javax.swing.DefaultCellEditor(new JCheckBox()) { + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + JButton button = new JButton("Cấp thuốc"); + button.addActionListener(e -> { + String drugName = table.getValueAt(row, 1).toString(); + CheckoutPage.this.openConfigureItemDialog(drugName, row); + fireEditingStopped(); + }); + return button; + } + }); + + // COLOR-CODED STATUS RENDERER (highlight entire row) + prescriptionChecklistTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + + // Skip button column + if (column == 3) { + return prescriptionChecklistTable.getColumnModel().getColumn(3).getCellRenderer() + .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } + + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + // Get the status from the hidden status field (we'll store it in the model) + String itemName = table.getValueAt(row, 1).toString(); + String status = getItemStatus(itemName); // Helper method to get status + + if (status.contains("Hết hàng") || status.contains("Out of Stock")) { + c.setBackground(new Color(255, 230, 230)); // Light red + if (column == 1) { // Add warning icon to item name + setText("⚠️ " + value.toString()); + } + } else if (status.contains("Sắp hết") || status.contains("Low Stock")) { + c.setBackground(new Color(255, 250, 220)); // Light yellow + if (column == 1) { // Add warning icon to item name + setText("⚠️ " + value.toString()); + } + } else { + if (isSelected) { + c.setBackground(table.getSelectionBackground()); + } else { + c.setBackground(Color.WHITE); + } + if (column == 1 && !value.toString().startsWith("⚠️")) { + setText(value.toString()); + } + } + + c.setForeground(isSelected ? table.getSelectionForeground() : Color.BLACK); + return c; + } + }); + } + + private void setupDispensedTable() { + dispensedItemsTable.setRowHeight(30); // Increased row height + dispensedItemsTable.setFont(new Font("Arial", Font.PLAIN, 14)); // Increased font size + dispensedItemsTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); + dispensedItemsTable.getTableHeader().setBackground(new Color(76, 175, 80)); + dispensedItemsTable.getTableHeader().setForeground(Color.WHITE); + + // Set column widths + dispensedItemsTable.getColumnModel().getColumn(0).setPreferredWidth(200); + dispensedItemsTable.getColumnModel().getColumn(1).setPreferredWidth(70); + dispensedItemsTable.getColumnModel().getColumn(2).setPreferredWidth(90); + dispensedItemsTable.getColumnModel().getColumn(3).setPreferredWidth(70); + dispensedItemsTable.getColumnModel().getColumn(4).setPreferredWidth(90); + dispensedItemsTable.getColumnModel().getColumn(5).setPreferredWidth(110); + + // Right-align numeric columns + DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer(); + rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT); + dispensedItemsTable.getColumnModel().getColumn(3).setCellRenderer(rightRenderer); // SL + dispensedItemsTable.getColumnModel().getColumn(4).setCellRenderer(rightRenderer); // Đơn giá + dispensedItemsTable.getColumnModel().getColumn(5).setCellRenderer(rightRenderer); // Thành tiền + } + + private void layoutComponents() { + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + // Top Panel - Search and Patient Info + JPanel topPanel = createTopPanel(); + mainPanel.add(topPanel, BorderLayout.NORTH); + + // Main Content - Three Panel Layout + JPanel contentPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); - gbc.insets = new Insets(5,5,5,5); - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.anchor = GridBagConstraints.WEST; - - // Bill Info (ID, Date, Customer for standalone) - gbc.gridx = 0; gbc.gridy = 0; billInfoAndActionsPanel.add(new JLabel("Mã HĐ:"), gbc); - gbc.gridx = 1; billIdLabel = new JLabel("N/A"); billInfoAndActionsPanel.add(billIdLabel, gbc); - gbc.gridx = 2; billInfoAndActionsPanel.add(new JLabel("Ngày:"), gbc); - gbc.gridx = 3; billDateLabel = new JLabel(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); billInfoAndActionsPanel.add(billDateLabel, gbc); - - gbc.gridx = 0; gbc.gridy = 1; billInfoAndActionsPanel.add(new JLabel("Tên KH (lẻ):"), gbc); - gbc.gridx = 1; gbc.gridwidth=3; customerNameField = new JTextField(20); billInfoAndActionsPanel.add(customerNameField, gbc); gbc.gridwidth=1; - gbc.gridx = 0; gbc.gridy = 2; billInfoAndActionsPanel.add(new JLabel("SĐT KH (lẻ):"), gbc); - gbc.gridx = 1; gbc.gridwidth=3; customerPhoneField = new JTextField(15); billInfoAndActionsPanel.add(customerPhoneField, gbc); gbc.gridwidth=1; - - // Barcode Scan - gbc.gridx = 0; gbc.gridy = 3; billInfoAndActionsPanel.add(new JLabel("Barcode:"), gbc); - gbc.gridx = 1; gbc.gridwidth=2; barcodeField = new JTextField(20); billInfoAndActionsPanel.add(barcodeField, gbc); - gbc.gridx = 3; gbc.gridwidth=1; scanBarcodeButton = new JButton("Quét"); billInfoAndActionsPanel.add(scanBarcodeButton, gbc); - scanBarcodeButton.addActionListener(e -> scanBarcodeAction()); - gbc.gridwidth=1; - - // Bill Items Table - String[] billItemCols = {"Mã Thuốc", "Tên thuốc", "SL", "ĐVT", "Đơn giá", "Thành tiền"}; - billItemsTableModel = new DefaultTableModel(new Object[][]{}, billItemCols) { - public boolean isCellEditable(int row, int column) { return false; } // Or allow SL edit - }; - billItemsTable = new JTable(billItemsTableModel); - billItemsTable.setRowHeight(25); - billItemsTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); - billItemsTable.setFont(new Font("Arial", Font.PLAIN, 12)); - JScrollPane billItemsScroll = new JScrollPane(billItemsTable); - - gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 4; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; - billInfoAndActionsPanel.add(billItemsScroll, gbc); - gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weighty = 0.0; + gbc.insets = new Insets(8, 8, 8, 8); - // Add/Remove Medicine Buttons for Bill Items - JPanel itemActionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - addMedicineItemButton = new JButton("Thêm thuốc vào HĐ"); - addMedicineItemButton.addActionListener(e -> openStandaloneMedicineDialog()); - removeMedicineItemButton = new JButton("Xóa thuốc khỏi HĐ"); - removeMedicineItemButton.addActionListener(e -> removeMedicineFromBillAction()); - itemActionPanel.add(addMedicineItemButton); - itemActionPanel.add(removeMedicineItemButton); - gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 4; billInfoAndActionsPanel.add(itemActionPanel, gbc); + // Left Panel - Prescription Checklist + gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.35; gbc.weighty = 1.0; + contentPanel.add(createPrescriptionPanel(), gbc); + // Middle Panel - Dispensed Items + gbc.gridx = 1; gbc.weightx = 0.4; + contentPanel.add(createDispensedPanel(), gbc); - // Totals Panel - JPanel totalsPanel = new JPanel(new GridBagLayout()); - GridBagConstraints totalGbc = new GridBagConstraints(); - totalGbc.insets = new Insets(3,10,3,10); - totalGbc.anchor = GridBagConstraints.EAST; - - totalGbc.gridx=0; totalGbc.gridy=0; totalsPanel.add(new JLabel("Tổng phụ:"), totalGbc); - totalGbc.gridx=1; subtotalAmountLabel = new JLabel("0 VNĐ"); totalsPanel.add(subtotalAmountLabel, totalGbc); - totalGbc.gridx=0; totalGbc.gridy++; totalsPanel.add(new JLabel("Giảm giá:"), totalGbc); - totalGbc.gridx=1; discountField = new JTextField("0", 8); discountField.setHorizontalAlignment(JTextField.RIGHT); totalsPanel.add(discountField, totalGbc); - // TODO: Add listener to discountField to update grand total - totalGbc.gridx=0; totalGbc.gridy++; totalsPanel.add(new JLabel("TỔNG CỘNG:"), totalGbc); - totalGbc.gridx=1; grandTotalAmountLabel = new JLabel("0 VNĐ"); - grandTotalAmountLabel.setFont(new Font("Arial", Font.BOLD, 16)); - grandTotalAmountLabel.setForeground(Color.RED); - totalsPanel.add(grandTotalAmountLabel, totalGbc); - - gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.EAST; - billInfoAndActionsPanel.add(totalsPanel, gbc); + // Right Panel - Payment Summary + gbc.gridx = 2; gbc.weightx = 0.25; + contentPanel.add(createPaymentPanel(), gbc); - - // Main Action Buttons (Payment, Print) - JPanel mainActionsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); - processPaymentButton = new JButton("Thanh Toán"); - processPaymentButton.setFont(new Font("Arial", Font.BOLD, 16)); - processPaymentButton.setBackground(new Color(76, 175, 80)); - processPaymentButton.setForeground(Color.WHITE); - processPaymentButton.addActionListener(e -> processPaymentAction()); - - printBillButton = new JButton("In Hóa Đơn"); - printBillButton.setFont(new Font("Arial", Font.BOLD, 16)); - printBillButton.setBackground(new Color(33, 150, 243)); - printBillButton.setForeground(Color.WHITE); - printBillButton.addActionListener(e -> printBillAction()); - - mainActionsPanel.add(processPaymentButton); - mainActionsPanel.add(printBillButton); - - rightPanel.add(billInfoAndActionsPanel, BorderLayout.CENTER); - rightPanel.add(mainActionsPanel, BorderLayout.SOUTH); - - mainSplitPane.setRightComponent(new JScrollPane(rightPanel)); // Make right panel scrollable too - - add(mainSplitPane, BorderLayout.CENTER); - - // TODO: Load initial finished checkup queue - loadFinishedCheckupQueue(); - updateBillDetailsPanel(null); // Initially no bill selected or new bill + mainPanel.add(contentPanel, BorderLayout.CENTER); + add(mainPanel, BorderLayout.CENTER); } - private void loadFinishedCheckupQueue() { - // TODO: Network request to get patients with status like "ĐÃ KHÁM XONG" or "CHỜ THANH TOÁN" - // For now, add dummy data - finishedCheckupTableModel.addRow(new Object[]{"CK001", "Nguyễn Văn A", "250,000 VNĐ", "Chờ TT"}); - finishedCheckupTableModel.addRow(new Object[]{"CK002", "Trần Thị B", "180,000 VNĐ", "Chờ TT"}); - log.info("Dummy finished checkup queue loaded."); + private JPanel createTopPanel() { + JPanel topPanel = new JPanel(new BorderLayout(15, 15)); + topPanel.setBackground(Color.WHITE); + topPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(224, 224, 224)), + BorderFactory.createEmptyBorder(20, 20, 20, 20) + )); + + JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + searchPanel.setOpaque(false); + + JLabel searchLabel = new JLabel("Tìm bệnh nhân (ID/Tên) hoặc quét mã (F2):"); + searchLabel.setFont(new Font("Arial", Font.BOLD, 16)); // Increased font size + searchPanel.add(searchLabel); + searchPanel.add(Box.createHorizontalStrut(15)); + searchPanel.add(searchPatientField); + + topPanel.add(searchPanel, BorderLayout.WEST); + topPanel.add(patientInfoLabel, BorderLayout.EAST); + + return topPanel; } - private void createNewStandaloneBill() { - log.info("Creating new standalone bill."); - // TODO: Generate a new Bill ID (e.g., "BL" + timestamp or sequence) - billIdLabel.setText("BL" + System.currentTimeMillis()%10000); - billDateLabel.setText(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); - customerNameField.setText(""); - customerPhoneField.setText(""); - customerNameField.setEditable(true); - customerPhoneField.setEditable(true); - barcodeField.setText(""); - billItemsTableModel.setRowCount(0); // Clear items table - subtotalAmountLabel.setText("0 VNĐ"); - discountField.setText("0"); - grandTotalAmountLabel.setText("0 VNĐ"); - // TODO: Clear/reset any other relevant fields for a new bill - // TODO: Focus on customer name or barcode field - } - - private void updateBillDetailsPanel(/* BillObject bill/Patient patient */ Object selectedBillData) { - if (selectedBillData == null) { // For new bill or initial state - billIdLabel.setText("N/A"); - billDateLabel.setText(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); - customerNameField.setText(""); customerNameField.setEditable(true); - customerPhoneField.setText(""); customerPhoneField.setEditable(true); - barcodeField.setText(""); - billItemsTableModel.setRowCount(0); - updateTotals(); - } else { - // TODO: Populate right panel based on selectedBillData (from finished queue) - // Example: - // billIdLabel.setText(bill.getId()); - // billDateLabel.setText(bill.getDate()); - // customerNameField.setText(patient.getName()); customerNameField.setEditable(false); - // customerPhoneField.setText(patient.getPhone()); customerPhoneField.setEditable(false); - // billItemsTableModel.setDataVector(bill.getItems(), billItemCols); - // updateTotals(); - log.info("Loading bill details for: " + selectedBillData.toString()); - } + private JPanel createPrescriptionPanel() { + RoundedPanel panel = new RoundedPanel(12, Color.WHITE, true); + panel.setLayout(new BorderLayout(8, 8)); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(63, 81, 181), 2), + BorderFactory.createEmptyBorder(15, 15, 15, 15) + )); + + JLabel titleLabel = new JLabel("ĐƠN THUỐC CẦN XUẤT", SwingConstants.CENTER); + titleLabel.setFont(new Font("Arial", Font.BOLD, 18)); // Increased font size + titleLabel.setForeground(new Color(63, 81, 181)); + titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0,15, 0)); + + JScrollPane tableScroll = new JScrollPane(prescriptionChecklistTable); + tableScroll.setPreferredSize(new Dimension(420, 320)); + + panel.add(titleLabel, BorderLayout.NORTH); + panel.add(tableScroll, BorderLayout.CENTER); + panel.add(doctorNotesArea, BorderLayout.SOUTH); + + return panel; } + private JPanel createDispensedPanel() { + RoundedPanel panel = new RoundedPanel(12, Color.WHITE, true); + panel.setLayout(new BorderLayout(8, 8)); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(76, 175, 80), 2), + BorderFactory.createEmptyBorder(15, 15, 15, 15) + )); - private void scanBarcodeAction() { - String barcode = barcodeField.getText().trim(); - if (barcode.isEmpty()) { - JOptionPane.showMessageDialog(this, "Vui lòng nhập barcode.", "Barcode trống", JOptionPane.WARNING_MESSAGE); - return; - } - // TODO: Network request to find medicine by barcode - // If found, add to billItemsTable or open dialog with prefilled info - log.info("Barcode scanned (mock): " + barcode); - JOptionPane.showMessageDialog(this, "Đã quét barcode (mock): " + barcode + "\nTODO: Tìm thuốc và thêm vào HĐ.", "Quét Barcode", JOptionPane.INFORMATION_MESSAGE); + JLabel titleLabel = new JLabel("THUỐC ĐÃ LẤY", SwingConstants.CENTER); + titleLabel.setFont(new Font("Arial", Font.BOLD, 18)); // Increased font size + titleLabel.setForeground(new Color(76, 175, 80)); + titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0)); + + JScrollPane tableScroll = new JScrollPane(dispensedItemsTable); + tableScroll.setPreferredSize(new Dimension(480, 380)); + + panel.add(titleLabel, BorderLayout.NORTH); + panel.add(tableScroll, BorderLayout.CENTER); + + return panel; } - - private void openStandaloneMedicineDialog() { - if (standaloneMedicineDialog == null) { - standaloneMedicineDialog = new StandaloneMedicineDialog(mainFrame); - } - standaloneMedicineDialog.setVisible(true); - String[][] selectedMeds = standaloneMedicineDialog.getMedicinePrescription(); - if (selectedMeds != null) { - for (String[] med : selectedMeds) { - // med: ID, Tên thuốc, SL, ĐVT, Sáng, Trưa, Chiều, Đơn giá, Thành tiền, Ghi chú - // For bill, we might only need: ID, Tên thuốc, SL, ĐVT, Đơn giá, Thành tiền - if (med.length >= 9) { - billItemsTableModel.addRow(new Object[]{ - med[0], // ID - med[1], // Tên thuốc - med[2], // SL - med[3], // ĐVT - med[7], // Đơn giá - med[8] // Thành tiền - }); + + private JPanel createPaymentPanel() { + RoundedPanel panel = new RoundedPanel(12, Color.WHITE, true); + panel.setLayout(new BorderLayout(8, 8)); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(244, 67, 54), 2), + BorderFactory.createEmptyBorder(15, 15, 15, 15) + )); + + JLabel titleLabel = new JLabel("THANH TOÁN", SwingConstants.CENTER); + titleLabel.setFont(new Font("Arial", Font.BOLD, 18)); // Increased font size + titleLabel.setForeground(new Color(244, 67, 54)); + + JPanel summaryPanel = new JPanel(new GridBagLayout()); + summaryPanel.setOpaque(false); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 8, 10, 8); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Cost breakdown with RIGHT-ALIGNED values + gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.6; + JLabel medicineLabel = new JLabel("Tổng tiền thuốc:"); + medicineLabel.setFont(new Font("Arial", Font.PLAIN, 16)); + summaryPanel.add(medicineLabel, gbc); + gbc.gridx = 1; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.4; + summaryPanel.add(medicineSubtotalLabel, gbc); + + gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.6; + JLabel serviceLabel = new JLabel("Tổng tiền dịch vụ:"); + serviceLabel.setFont(new Font("Arial", Font.PLAIN, 16)); + summaryPanel.add(serviceLabel, gbc); + gbc.gridx = 1; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.4; + summaryPanel.add(serviceSubtotalLabel, gbc); + + // Separator line + gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.insets = new Insets(15, 0, 15, 0); + JSeparator separator = new JSeparator(); + separator.setForeground(Color.GRAY); + summaryPanel.add(separator, gbc); + + // Grand total + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 1; gbc.insets = new Insets(10, 8, 10, 8); + gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.6; + JLabel totalLabel = new JLabel("TỔNG CỘNG:"); + totalLabel.setFont(new Font("Arial", Font.BOLD, 18)); + summaryPanel.add(totalLabel, gbc); + gbc.gridx = 1; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.4; + summaryPanel.add(grandTotalLabel, gbc); + + // Payment input + gbc.gridx = 0; gbc.gridy = 4; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.6; + JLabel cashLabel = new JLabel("Tiền khách đưa:"); + cashLabel.setFont(new Font("Arial", Font.PLAIN, 16)); + summaryPanel.add(cashLabel, gbc); + gbc.gridx = 1; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.4; + summaryPanel.add(cashReceivedField, gbc); + + gbc.gridx = 0; gbc.gridy = 5; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.6; + JLabel changeTextLabel = new JLabel("Tiền thối lại:"); + changeTextLabel.setFont(new Font("Arial", Font.BOLD, 16)); + summaryPanel.add(changeTextLabel, gbc); + gbc.gridx = 1; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.4; + summaryPanel.add(changeLabel, gbc); + + // Action buttons + JPanel buttonPanel = new JPanel(new GridLayout(2, 1, 0, 15)); + buttonPanel.setOpaque(false); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(25, 0, 0, 0)); + buttonPanel.add(payAndPrintButton); + buttonPanel.add(cancelButton); + + panel.add(titleLabel, BorderLayout.NORTH); + panel.add(summaryPanel, BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.SOUTH); + + return panel; + } + + private void setupEventListeners() { + // Search field - Enter key or barcode scan + searchPatientField.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) {} + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + searchAndLoadPatient(); } } - updateTotals(); + + @Override + public void keyReleased(KeyEvent e) {} + }); + + // Cash received field - calculate change + cashReceivedField.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) {} + + @Override + public void keyPressed(KeyEvent e) {} + + @Override + public void keyReleased(KeyEvent e) { + calculateChange(); + } + }); + + // Prescription checklist - monitor completion + prescriptionChecklistModel.addTableModelListener(e -> { + updatePayButtonState(); + }); + + // Cancel button + cancelButton.addActionListener(e -> clearCurrentTransaction()); + + // Pay and print button + payAndPrintButton.addActionListener(e -> processPaymentAndPrint()); + } + + // KEYBOARD SHORTCUTS IMPLEMENTATION + private void setupKeyboardShortcuts() { + InputMap inputMap = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = this.getActionMap(); + + // F2 or Ctrl+F: Focus search field + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "focusSearch"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK), "focusSearch"); + actionMap.put("focusSearch", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + searchPatientField.requestFocusInWindow(); + searchPatientField.selectAll(); + } + }); + + // F9 or Ctrl+P: Activate payment button + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0), "processPayment"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK), "processPayment"); + actionMap.put("processPayment", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (payAndPrintButton.isEnabled()) { + processPaymentAndPrint(); + } + } + }); + + // ESC: Cancel current transaction + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancelTransaction"); + actionMap.put("cancelTransaction", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + clearCurrentTransaction(); + } + }); + } + + private void searchAndLoadPatient() { + String searchTerm = searchPatientField.getText().trim(); + if (searchTerm.isEmpty()) { + playErrorSound(); + JOptionPane.showMessageDialog(this, "Vui lòng nhập ID bệnh nhân hoặc quét mã.", "Lỗi", JOptionPane.WARNING_MESSAGE); + return; + } + + // BARCODE SCAN DETECTION: If term looks like a drug barcode and we have a patient loaded + if (searchTerm.length() > 8 && searchTerm.matches("\\d+") && !currentPatientId.isEmpty()) { + handleBarcodeScanned(searchTerm); + return; + } + + // TODO: Network request to search patient by ID/name/barcode + log.info("Searching for patient: " + searchTerm); + + // Mock data for demonstration + loadMockPatientData(searchTerm); + playSuccessSound(); + } + + private void handleBarcodeScanned(String barcode) { + log.info("Barcode scanned: " + barcode); + + // TODO: Network request to identify drug from barcode + String drugName = identifyDrugFromBarcode(barcode); + + if (drugName == null) { + playErrorSound(); + JOptionPane.showMessageDialog(this, + "Không tìm thấy thuốc với mã vạch: " + barcode, + "Mã vạch không hợp lệ", + JOptionPane.ERROR_MESSAGE); + searchPatientField.setText(""); + return; + } + + // SAFETY CHECK: Verify drug exists in prescription + int prescriptionRow = findDrugInPrescription(drugName); + if (prescriptionRow == -1) { + playErrorSound(); + JOptionPane.showMessageDialog(this, + "CẢNH BÁO: Thuốc '" + drugName + "' KHÔNG có trong đơn thuốc của bệnh nhân!\n\n" + + "Vui lòng kiểm tra lại hoặc liên hệ bác sĩ.", + "Thuốc không có trong đơn", + JOptionPane.ERROR_MESSAGE); + searchPatientField.setText(""); + return; + } + + // Check if already dispensed + Boolean isChecked = (Boolean) prescriptionChecklistModel.getValueAt(prescriptionRow, 0); + if (isChecked != null && isChecked) { + playErrorSound(); + JOptionPane.showMessageDialog(this, + "Thuốc '" + drugName + "' đã được cấp rồi!", + "Thuốc đã cấp", + JOptionPane.WARNING_MESSAGE); + searchPatientField.setText(""); + return; + } + + // Valid drug - open configure dialog + playSuccessSound(); + searchPatientField.setText(""); + openConfigureItemDialog(drugName, prescriptionRow); + } + + private String identifyDrugFromBarcode(String barcode) { + // TODO: Implement actual barcode lookup + // Mock implementation + switch (barcode) { + case "1234567890": return "Paracetamol 500mg"; + case "0987654321": return "Amoxicillin 250mg"; + default: return null; } } - private void removeMedicineFromBillAction() { - int selectedRow = billItemsTable.getSelectedRow(); - if (selectedRow != -1) { - billItemsTableModel.removeRow(selectedRow); - updateTotals(); - } else { - JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc để xóa khỏi hóa đơn.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); - } - } - - private void updateTotals() { - double subtotal = 0; - for (int i = 0; i < billItemsTableModel.getRowCount(); i++) { - try { - // Assuming "Thành tiền" is at column 5 and is a string like "120,000" - String totalStr = billItemsTableModel.getValueAt(i, 5).toString().replace(",", ""); - subtotal += Double.parseDouble(totalStr); - } catch (Exception e) { - log.error("Error calculating subtotal for row " + i, e); + private int findDrugInPrescription(String drugName) { + for (int i = 0; i < prescriptionChecklistModel.getRowCount(); i++) { + String prescriptionDrug = prescriptionChecklistModel.getValueAt(i, 1).toString(); + // Remove warning icons for comparison + prescriptionDrug = prescriptionDrug.replace("⚠️ ", ""); + if (prescriptionDrug.equals(drugName)) { + return i; } } - subtotalAmountLabel.setText(String.format("%,.0f VNĐ", subtotal)); + return -1; + } + + private void openConfigureItemDialog(String drugName, int prescriptionRow) { + currentDispensingDrug = drugName; - double discount = 0; - try { - discount = Double.parseDouble(discountField.getText().replace(",", "")); - } catch (NumberFormatException e) { - // ignore, keep discount 0 + if (configureItemDialog == null) { + createConfigureItemDialog(); } - double grandTotal = subtotal - discount; - grandTotalAmountLabel.setText(String.format("%,.0f VNĐ", grandTotal)); + // Populate dialog with drug data + populateConfigureDialog(drugName); + + // Center dialog on parent + configureItemDialog.setLocationRelativeTo(this); + configureItemDialog.setVisible(true); } - private void processPaymentAction() { - // TODO: Implement payment processing logic - // - Validate bill (items, total > 0) - // - Send request to backend to mark bill as paid, update inventory - // - Show success/failure message - // - Potentially clear the bill or offer to print - String billId = billIdLabel.getText(); - String total = grandTotalAmountLabel.getText(); - log.info("Processing payment for Bill ID: " + billId + ", Amount: " + total); - JOptionPane.showMessageDialog(this, "THANH TOÁN THÀNH CÔNG (mock)\nBill ID: "+billId+"\nTổng tiền: "+total+"\nTODO: Lưu HĐ, cập nhật kho...", "Xác nhận thanh toán", JOptionPane.INFORMATION_MESSAGE); - createNewStandaloneBill(); // Clear for next bill + private void createConfigureItemDialog() { + configureItemDialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(this), + "Xác nhận chi tiết thuốc", true); + configureItemDialog.setSize(500, 400); + configureItemDialog.setLayout(new BorderLayout(10, 10)); + + // Title panel + JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + titlePanel.setBackground(new Color(63, 81, 181)); + JLabel titleLabel = new JLabel("Xác nhận chi tiết: "); + titleLabel.setFont(new Font("Arial", Font.BOLD, 16)); + titleLabel.setForeground(Color.WHITE); + titlePanel.add(titleLabel); + configureItemDialog.add(titlePanel, BorderLayout.NORTH); + + // Main content panel + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + // Part 1: Unit & Quantity Selection + JPanel part1Panel = new JPanel(new GridBagLayout()); + part1Panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(63, 81, 181)), + "PHẦN 1: CHỌN ĐƠN VỊ & SỐ LƯỢNG", + javax.swing.border.TitledBorder.LEFT, + javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), + new Color(63, 81, 181) + )); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(8, 8, 8, 8); + gbc.anchor = GridBagConstraints.WEST; + + // Unit selection + gbc.gridx = 0; gbc.gridy = 0; + part1Panel.add(new JLabel("Đơn vị:"), gbc); + gbc.gridx = 1; + unitComboBox = new JComboBox<>(new String[]{"Hộp (10 vỉ)", "Vỉ (10 viên)", "Viên"}); + unitComboBox.setPreferredSize(new Dimension(150, 25)); + unitComboBox.addActionListener(e -> updateUnitPrice()); + part1Panel.add(unitComboBox, gbc); + + gbc.gridx = 2; + part1Panel.add(new JLabel("Đơn giá:"), gbc); + gbc.gridx = 3; + unitPriceLabel = new JLabel("100,000 VNĐ"); + unitPriceLabel.setFont(new Font("Arial", Font.BOLD, 14)); + part1Panel.add(unitPriceLabel, gbc); + + // Quantity selection + gbc.gridx = 0; gbc.gridy = 1; + part1Panel.add(new JLabel("Số lượng:"), gbc); + gbc.gridx = 1; + quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 999, 1)); + quantitySpinner.setPreferredSize(new Dimension(80, 25)); + quantitySpinner.addChangeListener(e -> updateLineTotal()); + part1Panel.add(quantitySpinner, gbc); + + gbc.gridx = 2; + part1Panel.add(new JLabel("Thành tiền:"), gbc); + gbc.gridx = 3; + lineTotalLabel = new JLabel("100,000 VNĐ"); + lineTotalLabel.setFont(new Font("Arial", Font.BOLD, 14)); + lineTotalLabel.setForeground(new Color(244, 67, 54)); + part1Panel.add(lineTotalLabel, gbc); + + // Part 2: Batch Selection + JPanel part2Panel = new JPanel(new BorderLayout(5, 5)); + part2Panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(76, 175, 80)), + "PHẦN 2: CHỌN LÔ SẢN XUẤT (Hệ thống đề xuất lô sắp hết hạn trước - FEFO)", + javax.swing.border.TitledBorder.LEFT, + javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), + new Color(76, 175, 80) + )); + + String[] batchColumns = {"Chọn", "Lô", "HSD", "Tồn kho"}; + batchTableModel = new DefaultTableModel(batchColumns, 0) { + @Override + public Class getColumnClass(int column) { + return column == 0 ? Boolean.class : String.class; + } + @Override + public boolean isCellEditable(int row, int column) { + return column == 0; // Only radio button is editable + } + }; + + batchTable = new JTable(batchTableModel); + batchTable.setRowHeight(25); + batchTable.setFont(new Font("Arial", Font.PLAIN, 13)); + batchTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 13)); + batchTable.getColumnModel().getColumn(0).setPreferredWidth(50); + batchTable.getColumnModel().getColumn(1).setPreferredWidth(100); + batchTable.getColumnModel().getColumn(2).setPreferredWidth(100); + batchTable.getColumnModel().getColumn(3).setPreferredWidth(100); + + // Single selection for batches + batchTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + JScrollPane batchScroll = new JScrollPane(batchTable); + batchScroll.setPreferredSize(new Dimension(400, 120)); + part2Panel.add(batchScroll, BorderLayout.CENTER); + + // Combine parts + JPanel contentPanel = new JPanel(new BorderLayout(10, 10)); + contentPanel.add(part1Panel, BorderLayout.NORTH); + contentPanel.add(part2Panel, BorderLayout.CENTER); + + mainPanel.add(contentPanel, BorderLayout.CENTER); + + // Button panel + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); + cancelDialogButton = new JButton("Hủy Bỏ"); + cancelDialogButton.setFont(new Font("Arial", Font.PLAIN, 14)); + cancelDialogButton.addActionListener(e -> configureItemDialog.setVisible(false)); + + confirmAddButton = new JButton("Xác Nhận & Thêm"); + confirmAddButton.setFont(new Font("Arial", Font.BOLD, 14)); + confirmAddButton.setBackground(new Color(76, 175, 80)); + confirmAddButton.setForeground(Color.WHITE); + confirmAddButton.setFocusPainted(false); + confirmAddButton.addActionListener(e -> confirmAndAddItem()); + + buttonPanel.add(cancelDialogButton); + buttonPanel.add(confirmAddButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + configureItemDialog.add(mainPanel, BorderLayout.CENTER); } - private void printBillAction() { - // TODO: Implement bill printing - // - Gather all necessary bill data (customer, items, totals) - // - Create a new Invoice/Bill printing class (similar to MedicineInvoice but for general bills) - // - Generate and show/print the PDF - log.info("Printing bill (mock) for Bill ID: " + billIdLabel.getText()); - JOptionPane.showMessageDialog(this, "Đang chuẩn bị in hóa đơn (mock)...\nTODO: Tạo file PDF hóa đơn.", "In Hóa Đơn", JOptionPane.INFORMATION_MESSAGE); + private void populateConfigureDialog(String drugName) { + // Update title + JLabel titleLabel = (JLabel) ((JPanel) configureItemDialog.getContentPane() + .getComponent(0)).getComponent(0); + titleLabel.setText("Xác nhận chi tiết: " + drugName); + + // TODO: Load actual unit options and prices from database + // Mock data for demonstration + currentUnitPrice = 100000.0; // Default price for first unit + updateUnitPrice(); + updateLineTotal(); + + // Load available batches with FEFO logic + loadAvailableBatches(drugName); + } + + private void loadAvailableBatches(String drugName) { + // Clear existing data + batchTableModel.setRowCount(0); + + // TODO: Load actual batches from inventory database + // Mock data - sorted by expiry date (FEFO) + if (drugName.contains("Paracetamol")) { + batchTableModel.addRow(new Object[]{true, "PA202408", "15/08/2025", "45 hộp"}); // FEFO selected + batchTableModel.addRow(new Object[]{false, "PA202411", "30/11/2025", "18 hộp"}); + } else if (drugName.contains("Amoxicillin")) { + batchTableModel.addRow(new Object[]{true, "AM202407", "20/07/2025", "12 hộp"}); // FEFO selected + batchTableModel.addRow(new Object[]{false, "AM202410", "15/10/2025", "35 hộp"}); + } + + // Ensure only one batch is selected (radio button behavior) + setupSingleBatchSelection(); + } + + private void setupSingleBatchSelection() { + batchTableModel.addTableModelListener(e -> { + if (e.getColumn() == 0) { // Selection column + int changedRow = e.getFirstRow(); + Boolean newValue = (Boolean) batchTableModel.getValueAt(changedRow, 0); + + if (newValue != null && newValue) { + // Uncheck all other rows + for (int i = 0; i < batchTableModel.getRowCount(); i++) { + if (i != changedRow) { + batchTableModel.setValueAt(false, i, 0); + } + } + } + } + }); + } + + private void updateUnitPrice() { + // TODO: Get actual price from database based on selected unit + String selectedUnit = (String) unitComboBox.getSelectedItem(); + if (selectedUnit.contains("Hộp")) { + currentUnitPrice = 100000.0; + } else if (selectedUnit.contains("Vỉ")) { + currentUnitPrice = 10000.0; + } else { + currentUnitPrice = 1000.0; + } + + unitPriceLabel.setText(currencyFormatter.format(currentUnitPrice) + " VNĐ"); + updateLineTotal(); + } + + private void updateLineTotal() { + int quantity = (Integer) quantitySpinner.getValue(); + double lineTotal = currentUnitPrice * quantity; + lineTotalLabel.setText(currencyFormatter.format(lineTotal) + " VNĐ"); + } + + private void confirmAndAddItem() { + // Validate batch selection + int selectedBatchRow = -1; + for (int i = 0; i < batchTableModel.getRowCount(); i++) { + Boolean selected = (Boolean) batchTableModel.getValueAt(i, 0); + if (selected != null && selected) { + selectedBatchRow = i; + break; + } + } + + if (selectedBatchRow == -1) { + JOptionPane.showMessageDialog(configureItemDialog, + "Vui lòng chọn lô sản xuất!", "Chưa chọn lô", JOptionPane.WARNING_MESSAGE); + return; + } + + // Get selected data + String drugName = currentDispensingDrug; + String unit = (String) unitComboBox.getSelectedItem(); + int quantity = (Integer) quantitySpinner.getValue(); + String batch = batchTableModel.getValueAt(selectedBatchRow, 1).toString(); + String expiry = batchTableModel.getValueAt(selectedBatchRow, 2).toString(); + double unitPrice = currentUnitPrice; + double lineTotal = unitPrice * quantity; + + // Add to dispensed items table + dispensedItemsModel.addRow(new Object[]{ + drugName, + batch, + expiry, + quantity + " " + unit.split(" ")[0], // Extract unit name + currencyFormatter.format(unitPrice) + " VNĐ", + currencyFormatter.format(lineTotal) + " VNĐ" + }); + + // Mark prescription item as completed + int prescriptionRow = findDrugInPrescription(drugName); + if (prescriptionRow != -1) { + prescriptionChecklistModel.setValueAt(true, prescriptionRow, 0); + } + + // Update totals + if (drugName.contains("Dịch vụ")) { + serviceTotal += lineTotal; + } else { + medicineTotal += lineTotal; + } + updateTotals(); + updatePayButtonState(); + + // Close dialog + configureItemDialog.setVisible(false); + + // Success feedback + playSuccessSound(); + + // Refresh prescription table to update button states + prescriptionChecklistTable.repaint(); + + log.info("Added to dispensed items: " + drugName + " (Batch: " + batch + ", Qty: " + quantity + ")"); + } + + private void loadMockPatientData(String patientId) { + // Mock patient data + currentPatientId = patientId; + currentPatientName = "Nguyễn Văn A"; + currentDoctorName = "BS. Trần Thị Mai"; + + // IMPROVED PATIENT INFO DISPLAY + patientInfoLabel.setText(String.format( + "Bệnh nhân: %s | Mã BN: %s | Bác sĩ: %s", + currentPatientName, currentPatientId, currentDoctorName + )); + + // Clear previous data + prescriptionChecklistModel.setRowCount(0); + dispensedItemsModel.setRowCount(0); + doctorNotesArea.setText(""); + + // Mock prescription data (some automatically marked for services) + prescriptionChecklistModel.addRow(new Object[]{false, "Paracetamol 500mg", "2 hộp", "Cấp thuốc"}); + prescriptionChecklistModel.addRow(new Object[]{false, "Amoxicillin 250mg", "1 hộp", "Cấp thuốc"}); + prescriptionChecklistModel.addRow(new Object[]{true, "Dịch vụ khám tổng quát", "1 lần", "Đã cấp"}); // Auto-completed + + doctorNotesArea.setText("Uống thuốc sau ăn. Tránh uống cùng với sữa. Nếu có triệu chứng dị ứng, ngừng thuốc ngay. Tái khám sau 1 tuần."); + + updateTotals(); + updatePayButtonState(); + } + + private void calculateChange() { + try { + String cashText = cashReceivedField.getText().replace(",", "").trim(); + if (cashText.isEmpty()) { + changeLabel.setText("0 VNĐ"); + return; + } + + double cashReceived = Double.parseDouble(cashText); + double grandTotal = medicineTotal + serviceTotal; + double change = cashReceived - grandTotal; + + if (change >= 0) { + changeLabel.setText(currencyFormatter.format(change) + " VNĐ"); + changeLabel.setForeground(new Color(76, 175, 80)); + } else { + changeLabel.setText("THIẾU " + currencyFormatter.format(Math.abs(change)) + " VNĐ"); + changeLabel.setForeground(new Color(244, 67, 54)); + } + } catch (NumberFormatException e) { + changeLabel.setText("Số tiền không hợp lệ"); + changeLabel.setForeground(new Color(244, 67, 54)); + } + } + + private void updateTotals() { + medicineTotal = 0.0; + serviceTotal = 0.0; + + for (int i = 0; i < dispensedItemsModel.getRowCount(); i++) { + String drugName = dispensedItemsModel.getValueAt(i, 0).toString(); + double lineTotal = Double.parseDouble(dispensedItemsModel.getValueAt(i, 5).toString().replace(" VNĐ", "").replace(",", "")); + + if (drugName.contains("Dịch vụ")) { + serviceTotal += lineTotal; + } else { + medicineTotal += lineTotal; + } + } + + medicineSubtotalLabel.setText(currencyFormatter.format(medicineTotal) + " VNĐ"); + serviceSubtotalLabel.setText(currencyFormatter.format(serviceTotal) + " VNĐ"); + grandTotalLabel.setText(currencyFormatter.format(medicineTotal + serviceTotal) + " VNĐ"); + + calculateChange(); + } + + private void updatePayButtonState() { + // Check if all prescription items are completed + boolean allCompleted = true; + for (int i = 0; i < prescriptionChecklistModel.getRowCount(); i++) { + Boolean checked = (Boolean) prescriptionChecklistModel.getValueAt(i, 0); + if (checked == null || !checked) { + allCompleted = false; + break; + } + } + + payAndPrintButton.setEnabled(allCompleted && !currentPatientId.isEmpty()); + } + + private void clearCurrentTransaction() { + currentPatientId = ""; + currentPatientName = ""; + currentDoctorName = ""; + patientInfoLabel.setText("Chưa chọn bệnh nhân"); + searchPatientField.setText(""); + + prescriptionChecklistModel.setRowCount(0); + dispensedItemsModel.setRowCount(0); + doctorNotesArea.setText(""); + + cashReceivedField.setText(""); + changeLabel.setText("0 VNĐ"); + + medicineTotal = 0.0; + serviceTotal = 0.0; + updateTotals(); + updatePayButtonState(); + + searchPatientField.requestFocusInWindow(); + } + + private void processPaymentAndPrint() { + // TODO: Implement payment processing and receipt printing + log.info("Processing payment for patient: " + currentPatientName + " (ID: " + currentPatientId + ")"); + log.info("Total amount: " + currencyFormatter.format(medicineTotal + serviceTotal) + " VNĐ"); + + playSuccessSound(); + + JOptionPane.showMessageDialog(this, + "THANH TOÁN THÀNH CÔNG!\n\n" + + "Bệnh nhân: " + currentPatientName + "\n" + + "Tổng tiền: " + currencyFormatter.format(medicineTotal + serviceTotal) + " VNĐ\n\n" + + "TODO: Lưu hóa đơn và cập nhật tồn kho...", + "Xác nhận thanh toán", + JOptionPane.INFORMATION_MESSAGE); + + // Clear transaction after successful payment + clearCurrentTransaction(); + } + + // HELPER METHODS + private String getItemStatus(String itemName) { + // TODO: Get actual status from inventory system + if (itemName.contains("Amoxicillin")) { + return "Sắp hết hàng"; + } + return "Còn hàng"; + } + + // AUDIBLE FEEDBACK PLACEHOLDERS + private void playSuccessSound() { + // TODO: Implement pleasant beep sound + log.debug("Playing success sound"); + Toolkit.getDefaultToolkit().beep(); // Basic system beep for now + } + + private void playErrorSound() { + // TODO: Implement distinct error sound + log.debug("Playing error sound"); + for (int i = 0; i < 2; i++) { + Toolkit.getDefaultToolkit().beep(); + try { Thread.sleep(100); } catch (InterruptedException e) {} + } } } \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java b/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java deleted file mode 100644 index 71f07c5..0000000 --- a/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java +++ /dev/null @@ -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 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 getMedInfoResponseListener = this::getMedInfoHandler; - private TableColumnModel columnModel; - private HashMap 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 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); - }); - } -} \ No newline at end of file diff --git a/src/main/java/BsK/common/entity/Patient.java b/src/main/java/BsK/common/entity/Patient.java index 18a0413..0b9e80e 100644 --- a/src/main/java/BsK/common/entity/Patient.java +++ b/src/main/java/BsK/common/entity/Patient.java @@ -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]; + } } } diff --git a/src/main/java/BsK/common/packet/req/AddPatientRequest.java b/src/main/java/BsK/common/packet/req/AddPatientRequest.java index 74ae09b..5e3b5ff 100644 --- a/src/main/java/BsK/common/packet/req/AddPatientRequest.java +++ b/src/main/java/BsK/common/packet/req/AddPatientRequest.java @@ -15,4 +15,5 @@ public class AddPatientRequest implements Packet { String patientPhone; String patientAddress; String patientGender; + String patientCccdDdcn; } diff --git a/src/main/java/BsK/common/packet/req/SaveCheckupRequest.java b/src/main/java/BsK/common/packet/req/SaveCheckupRequest.java index 274711b..fd5c954 100644 --- a/src/main/java/BsK/common/packet/req/SaveCheckupRequest.java +++ b/src/main/java/BsK/common/packet/req/SaveCheckupRequest.java @@ -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; } \ No newline at end of file diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index dcfe2e3..401cebe 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -110,7 +110,7 @@ public class ServerHandler extends SimpleChannelInboundHandler