From 2f5d2bcf1b273dbd1bbb2484fd686e9b5c385d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:51:05 +0700 Subject: [PATCH] [update] Enhance CheckUpPage and AddDialog by integrating new patient fields including CCCD/DDCN, heart rate, and blood pressure measurements. Revise layout for improved data entry and user experience, ensuring comprehensive patient information management. Update data structures and server handling to accommodate new fields, enhancing overall functionality. --- .../CheckUpPage/AddDialog/AddDialog.java | 105 +- .../ui/component/CheckUpPage/CheckUpPage.java | 92 +- .../component/CheckoutPage/CheckoutPage.java | 1319 +++++++++++++---- .../StandaloneMedicineDialog.java | 646 -------- src/main/java/BsK/common/entity/Patient.java | 8 + .../common/packet/req/AddPatientRequest.java | 1 + .../common/packet/req/SaveCheckupRequest.java | 50 +- .../server/network/handler/ServerHandler.java | 26 +- src/main/resources/database/BSK.db | Bin 1187840 -> 1187840 bytes 9 files changed, 1241 insertions(+), 1006 deletions(-) delete mode 100644 src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/AddDialog/AddDialog.java index 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 SimpleChannelInboundHandlerL7Nue;ogDkvX0ze9Fm|q%GKf<; z9g&>kfMgU|PHF!Czy1GzMj&PaVrC#_0b*7lW&>h&Am#vKP9Ww2Vs0Sj0b*Vt<^y7W NAQssE|Gyxk0su?jtOfu8 delta 375 zcmZoT;L&ivV}i8cWd;UDB?bltP9SCk;_VZ4j2SO)Ojvkd5G2YxlYxI7Uo7td?w6bi zlZ765FIWBASfux%uOt+RLD-P zRM5yxO^MG;(d5#!G-MO^RiFHxO<}UkH_^>AA0KDsYGo5=7nhY~?2Mm$fmeBR@5>T4 zX(nd|E(T==)^9x1xF>U7<6O)k%>IBahRu*cd9%z1Gv?{K@Lo4?vF2OJs9l1BL