diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/MedicineDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/MedicineDialog.java index 8579eae..754276e 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/MedicineDialog.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/MedicineDialog/MedicineDialog.java @@ -27,6 +27,9 @@ import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; @Slf4j public class MedicineDialog extends JDialog { @@ -42,15 +45,20 @@ public class MedicineDialog extends JDialog { private JTextArea noteField; private DefaultTableModel tableModel, selectedTableModel; private String[] medcineColumns = {"ID", "Tên thuốc", "Công ty", "Mô tả", "Tồn kho", "ĐVT", "Giá"}; + private String[] suggestionColumns = {"Tên thuốc", "Tồn kho", "ĐVT", "Giá"}; private String[][] medicineData; private final ResponseListener getMedInfoResponseListener = this::getMedInfoHandler; private TableColumnModel columnModel; private HashMap selectedMedicine = new HashMap<>(); - private JTable medicineTable; - private JTable selectedTable; + private JTable chosenMedicineTable; + private JTable suggestionTable; + private DefaultTableModel suggestionTableModel; + private JPopupMenu suggestionPopup; private boolean isProgrammaticallySettingMedicineNameField = false; + private boolean isSelectionLocked = false; private List medicines = new ArrayList<>(); + private List filteredMedicines = new ArrayList<>(); private String[][] medicinePrescription; private String[][] originalPrescription; // Store original data for comparison private static final Logger logger = LoggerFactory.getLogger(MedicineDialog.class); @@ -66,18 +74,7 @@ public class MedicineDialog extends JDialog { // Get Medicine objects from the response medicines = response.getMedicines(); - // Set the data vector with the raw string arrays for backward compatibility - tableModel.setDataVector(medicineData, medcineColumns); - - // resize column width - columnModel = medicineTable.getColumnModel(); - columnModel.getColumn(0).setPreferredWidth(20); // ID - columnModel.getColumn(1).setPreferredWidth(150); // Name - columnModel.getColumn(2).setPreferredWidth(100); // Company - columnModel.getColumn(3).setPreferredWidth(200); // Description - columnModel.getColumn(4).setPreferredWidth(40); // Stock - columnModel.getColumn(5).setPreferredWidth(40); // Unit - columnModel.getColumn(6).setPreferredWidth(50); // Price + // We don't need to populate a main table anymore, just have the data ready. } private void sendGetMedInfoRequest() { @@ -106,10 +103,16 @@ public class MedicineDialog extends JDialog { private void init(final Frame parent) { setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(1100, 600); + setSize(1200, 700); 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); + // 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 @@ -149,7 +152,7 @@ public class MedicineDialog extends JDialog { 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) + titleFont, new Color(50, 50, 50) )); mainGbc.gridx = 0; mainGbc.gridy = 0; @@ -159,7 +162,7 @@ public class MedicineDialog extends JDialog { 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) + titleFont, new Color(50, 50, 50) )); mainGbc.gridy = 1; mainInputPanel.add(quantityPricePanel, mainGbc); @@ -168,7 +171,7 @@ public class MedicineDialog extends JDialog { dosagePanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Liều dùng", 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; mainInputPanel.add(dosagePanel, mainGbc); @@ -177,13 +180,15 @@ public class MedicineDialog extends JDialog { notePanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Ghi 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 = 3; mainInputPanel.add(notePanel, mainGbc); JPanel addMedicineButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JButton addMedicineButton = new JButton("Thêm thuốc"); + JButton addMedicineButton = new JButton("Thêm thuốc (F1)"); + addMedicineButton.setFont(labelFont); + addMedicineButton.setPreferredSize(new Dimension(200, 40)); addMedicineButtonPanel.add(addMedicineButton); mainGbc.gridy = 4; mainInputPanel.add(addMedicineButtonPanel, mainGbc); @@ -201,21 +206,29 @@ public class MedicineDialog extends JDialog { gbc.gridy = 0; gbc.gridx = 0; - medicineInfoPanel.add(new JLabel("Tên thuốc:"), gbc); + JLabel nameLabel = new JLabel("Tên thuốc:"); + nameLabel.setFont(labelFont); + medicineInfoPanel.add(nameLabel, gbc); gbc.gridx = 1; gbc.weightx = 1.0; medicineNameField = new JTextField(20); + medicineNameField.setFont(textFont); + medicineNameField.setPreferredSize(textFieldSize); medicineInfoPanel.add(medicineNameField, gbc); gbc.weightx = 0.0; gbc.gridy++; gbc.gridx = 0; - medicineInfoPanel.add(new JLabel("Công ty:"), gbc); + JLabel companyLabel = new JLabel("Công ty:"); + companyLabel.setFont(labelFont); + medicineInfoPanel.add(companyLabel, gbc); gbc.gridx = 1; gbc.weightx = 1.0; medicineCompanyField = new JTextField(20); + medicineCompanyField.setFont(textFont); + medicineCompanyField.setPreferredSize(textFieldSize); medicineCompanyField.setEditable(false); medicineInfoPanel.add(medicineCompanyField, gbc); gbc.weightx = 0.0; @@ -223,12 +236,15 @@ public class MedicineDialog extends JDialog { gbc.gridy++; gbc.gridx = 0; gbc.anchor = GridBagConstraints.NORTHWEST; - medicineInfoPanel.add(new JLabel("Mô tả:"), gbc); + JLabel descriptionLabel = new JLabel("Mô tả:"); + descriptionLabel.setFont(labelFont); + medicineInfoPanel.add(descriptionLabel, gbc); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 1; gbc.weightx = 1.0; medicineDescriptionField = new JTextArea(3, 20); + medicineDescriptionField.setFont(textFont); medicineDescriptionField.setLineWrap(true); medicineDescriptionField.setWrapStyleWord(true); medicineDescriptionField.setEditable(false); @@ -239,55 +255,74 @@ public class MedicineDialog extends JDialog { gbc.gridy = 0; gbc.gridx = 0; - quantityPricePanel.add(new JLabel("Số lượng:"), gbc); + JLabel quantityLabel = new JLabel("Số lượng:"); + quantityLabel.setFont(labelFont); + quantityPricePanel.add(quantityLabel, gbc); gbc.gridx = 1; gbc.weightx = 0.5; quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1)); - quantitySpinner.setPreferredSize(new Dimension(80, 25)); + quantitySpinner.setFont(textFont); + quantitySpinner.setPreferredSize(new Dimension(80, 30)); quantityPricePanel.add(quantitySpinner, gbc); gbc.gridx = 2; gbc.weightx = 0.0; - quantityPricePanel.add(new JLabel("Còn lại:"), gbc); + JLabel quantityLeftLabel = new JLabel("Còn lại:"); + quantityLeftLabel.setFont(labelFont); + quantityPricePanel.add(quantityLeftLabel, gbc); gbc.gridx = 3; gbc.weightx = 0.5; quantityLeftField = new JTextField(5); + quantityLeftField.setFont(textFont); + quantityLeftField.setPreferredSize(textFieldSize); quantityLeftField.setEditable(false); quantityPricePanel.add(quantityLeftField, gbc); gbc.weightx = 0.0; gbc.gridy++; gbc.gridx = 0; - quantityPricePanel.add(new JLabel("ĐVT:"), gbc); + JLabel unitLabel = new JLabel("ĐVT:"); + unitLabel.setFont(labelFont); + quantityPricePanel.add(unitLabel, 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.setFont(textFont); + UnitComboBox.setPreferredSize(textFieldSize); UnitComboBox.setEnabled(false); quantityPricePanel.add(UnitComboBox, gbc); gbc.gridx = 2; gbc.weightx = 0.0; - quantityPricePanel.add(new JLabel("Giá (VNĐ):"), gbc); + JLabel priceLabel = new JLabel("Giá (VNĐ):"); + priceLabel.setFont(labelFont); + quantityPricePanel.add(priceLabel, gbc); gbc.gridx = 3; gbc.weightx = 0.5; priceField = new JTextField(10); + priceField.setFont(textFont); + priceField.setPreferredSize(textFieldSize); 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); + JLabel totalLabel = new JLabel("Thành tiền (VNĐ):"); + totalLabel.setFont(labelFont); + quantityPricePanel.add(totalLabel, gbc); gbc.gridx = 1; gbc.gridwidth = 3; gbc.weightx = 1.0; totalField = new JTextField(10); + totalField.setFont(textFont); + totalField.setPreferredSize(textFieldSize); totalField.setEditable(false); quantityPricePanel.add(totalField, gbc); gbc.gridwidth = 1; @@ -297,33 +332,44 @@ public class MedicineDialog extends JDialog { gbc.gridy = 0; gbc.gridx = 0; gbc.weightx = 0.0; - dosagePanel.add(new JLabel("Sáng:"), gbc); + JLabel morningLabel = new JLabel("Sáng:"); + morningLabel.setFont(labelFont); + dosagePanel.add(morningLabel, gbc); gbc.gridx = 1; gbc.weightx = 0.3; morningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); - morningSpinner.setPreferredSize(new Dimension(60,25)); + morningSpinner.setFont(textFont); + morningSpinner.setPreferredSize(new Dimension(60,30)); dosagePanel.add(morningSpinner, gbc); gbc.gridx = 2; gbc.weightx = 0.0; - dosagePanel.add(new JLabel("Trưa:"), gbc); + JLabel noonLabel = new JLabel("Trưa:"); + noonLabel.setFont(labelFont); + dosagePanel.add(noonLabel, gbc); gbc.gridx = 3; gbc.weightx = 0.3; noonSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); - noonSpinner.setPreferredSize(new Dimension(60,25)); + noonSpinner.setFont(textFont); + noonSpinner.setPreferredSize(new Dimension(60,30)); dosagePanel.add(noonSpinner, gbc); gbc.gridx = 4; gbc.weightx = 0.0; - dosagePanel.add(new JLabel("Chiều:"), gbc); + JLabel eveningLabel = new JLabel("Chiều:"); + eveningLabel.setFont(labelFont); + dosagePanel.add(eveningLabel, gbc); gbc.gridx = 5; gbc.weightx = 0.3; eveningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); - eveningSpinner.setPreferredSize(new Dimension(60,25)); + eveningSpinner.setFont(textFont); + eveningSpinner.setPreferredSize(new Dimension(60,30)); dosagePanel.add(eveningSpinner, gbc); gbc.weightx = 0.0; gbc.gridy = 0; gbc.gridx = 0; - notePanel.add(new JLabel("Ghi chú:"), gbc); + JLabel noteLabel = new JLabel("Ghi chú:"); + noteLabel.setFont(labelFont); + notePanel.add(noteLabel, gbc); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 1; @@ -331,6 +377,7 @@ public class MedicineDialog extends JDialog { gbc.fill = GridBagConstraints.BOTH; gbc.weighty = 1.0; noteField = new JTextArea(3, 20); + noteField.setFont(textFont); noteField.setLineWrap(true); noteField.setWrapStyleWord(true); JScrollPane noteScrollPane = new JScrollPane(noteField); @@ -340,19 +387,24 @@ public class MedicineDialog extends JDialog { gbc.weightx = 0.0; gbc.weighty = 0.0; - tableModel = new DefaultTableModel(medicineData, medcineColumns) { + suggestionTableModel = new DefaultTableModel(new String[0][0], suggestionColumns) { @Override public boolean isCellEditable(int row, int column) { return false; } }; - medicineTable = new JTable(tableModel); - medicineTable.setFont(new Font("Arial", Font.PLAIN, 14)); - medicineTable.setRowHeight(25); - medicineTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - medicineTable.setAutoCreateRowSorter(true); - JScrollPane medicineTableScrollPane = new JScrollPane(medicineTable); - medicineTableScrollPane.setPreferredSize(new Dimension(600, 250)); + suggestionTable = new JTable(suggestionTableModel); + suggestionTable.setFont(new Font("Arial", Font.PLAIN, 15)); + suggestionTable.setRowHeight(30); + suggestionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + suggestionTable.setAutoCreateRowSorter(false); // We filter manually + JScrollPane suggestionScrollPane = new JScrollPane(suggestionTable); + + suggestionPopup = new JPopupMenu(); + suggestionPopup.setLayout(new BorderLayout()); + suggestionPopup.add(suggestionScrollPane, BorderLayout.CENTER); + suggestionPopup.setFocusable(false); + 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) { @@ -361,107 +413,134 @@ public class MedicineDialog extends JDialog { return column == 2 || column == 4 || column == 5 || column == 6 || column == 9; } }; - selectedTable = new JTable(selectedTableModel); - selectedTable.setFont(new Font("Arial", Font.PLAIN, 14)); - selectedTable.setRowHeight(25); - JScrollPane selectedTableScrollPane = new JScrollPane(selectedTable); + chosenMedicineTable = new JTable(selectedTableModel); + chosenMedicineTable.setFont(new Font("Arial", Font.PLAIN, 15)); + chosenMedicineTable.setRowHeight(30); + JScrollPane selectedTableScrollPane = new JScrollPane(chosenMedicineTable); selectedTableScrollPane.setPreferredSize(new Dimension(600, 150)); JPanel removeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton removeMedicineButton = new JButton("Xóa thuốc đã chọn"); + removeMedicineButton.setFont(labelFont); removeButtonPanel.add(removeMedicineButton); - // Create titled panels for right side sections - JPanel availableMedicinesPanel = new JPanel(new BorderLayout()); - availableMedicinesPanel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createEtchedBorder(), "Thuốc trong kho", - javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 14), new Color(63, 81, 181) - )); - availableMedicinesPanel.add(medicineTableScrollPane, BorderLayout.CENTER); - JPanel chosenMedicinesPanel = new JPanel(new BorderLayout()); chosenMedicinesPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Thuốc đã chọn", javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 14), new Color(220, 20, 60) + titleFont, new Color(220, 20, 60) )); chosenMedicinesPanel.setBackground(new Color(255, 240, 240)); // Light red background selectedTableScrollPane.setBackground(new Color(255, 240, 240)); // Light red background chosenMedicinesPanel.add(selectedTableScrollPane, BorderLayout.CENTER); chosenMedicinesPanel.add(removeButtonPanel, BorderLayout.SOUTH); - JPanel rightPanel = new JPanel(new BorderLayout(0, 5)); - rightPanel.add(availableMedicinesPanel, BorderLayout.CENTER); - rightPanel.add(chosenMedicinesPanel, 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.45); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mainInputScrollPane, chosenMedicinesPanel); + splitPane.setResizeWeight(0.35); add(splitPane, BorderLayout.CENTER); JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okButton = new JButton("Lưu"); + okButton.setFont(labelFont); JButton cancelButton = new JButton("Hủy"); + cancelButton.setFont(labelFont); bottomButtonPanel.add(okButton); bottomButtonPanel.add(cancelButton); add(bottomButtonPanel, BorderLayout.SOUTH); + // F1 Key binding for adding medicine + Action addAction = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + addSelectedMedicine(); + } + }; + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F1"), "addMedicineAction"); + getRootPane().getActionMap().put("addMedicineAction", addAction); + medicineNameField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) { - SwingUtilities.invokeLater(() -> filterMedicineTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } public void removeUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) { - SwingUtilities.invokeLater(() -> filterMedicineTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } public void insertUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) { - SwingUtilities.invokeLater(() -> filterMedicineTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } }); - medicineTable.addMouseListener(new MouseAdapter() { + suggestionTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1) { - SwingUtilities.invokeLater(() -> { - int selectedRow = medicineTable.getSelectedRow(); - if (selectedRow != -1) { - handleMedicineTableRowSelection(selectedRow); - } - }); - } + SwingUtilities.invokeLater(() -> { + int selectedRow = suggestionTable.getSelectedRow(); + if (selectedRow == -1) return; + + handleMedicineTableRowSelection(selectedRow); // Populate fields on any click + + if (e.getClickCount() == 2) { + addSelectedMedicine(); + suggestionPopup.setVisible(false); + } + }); } }); + medicineNameField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (medicineTable.getSelectedRow() != -1) addSelectedMedicine(); - } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { - if (medicineTable.getRowCount() > 0) { - int selectedRow = medicineTable.getSelectedRow(); - selectedRow = (selectedRow < medicineTable.getRowCount() - 1) ? selectedRow + 1 : 0; - medicineTable.setRowSelectionInterval(selectedRow, selectedRow); - medicineTable.scrollRectToVisible(medicineTable.getCellRect(selectedRow, 0, true)); - handleMedicineTableRowSelection(selectedRow); + if (suggestionPopup.isVisible()) { + int selectedRow = suggestionTable.getSelectedRow(); + int rowCount = suggestionTable.getRowCount(); + + switch (e.getKeyCode()) { + case KeyEvent.VK_DOWN: + if (rowCount > 0) { + selectedRow = (selectedRow + 1) % rowCount; + suggestionTable.setRowSelectionInterval(selectedRow, selectedRow); + suggestionTable.scrollRectToVisible(suggestionTable.getCellRect(selectedRow, 0, true)); + } + e.consume(); + break; + case KeyEvent.VK_UP: + if (rowCount > 0) { + selectedRow = (selectedRow - 1 + rowCount) % rowCount; + suggestionTable.setRowSelectionInterval(selectedRow, selectedRow); + suggestionTable.scrollRectToVisible(suggestionTable.getCellRect(selectedRow, 0, true)); + } + e.consume(); + break; + case KeyEvent.VK_ENTER: + if (selectedRow != -1) { + handleMedicineTableRowSelection(suggestionTable.getSelectedRow()); + suggestionPopup.setVisible(false); + quantitySpinner.requestFocusInWindow(); + } + e.consume(); + break; + case KeyEvent.VK_ESCAPE: + suggestionPopup.setVisible(false); + e.consume(); + break; } - } else if (e.getKeyCode() == KeyEvent.VK_UP) { - if (medicineTable.getRowCount() > 0) { - int selectedRow = medicineTable.getSelectedRow(); - selectedRow = (selectedRow > 0) ? selectedRow - 1 : medicineTable.getRowCount() - 1; - medicineTable.setRowSelectionInterval(selectedRow, selectedRow); - medicineTable.scrollRectToVisible(medicineTable.getCellRect(selectedRow, 0, true)); - handleMedicineTableRowSelection(selectedRow); + } else { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + handleExactMatch(); } } } @@ -473,6 +552,19 @@ public class MedicineDialog extends JDialog { addMedicineButton.addActionListener(e -> addSelectedMedicine()); removeMedicineButton.addActionListener(e -> removeSelectedMedicine()); + noteField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_TAB && e.getModifiersEx() == 0) { + e.consume(); + addSelectedMedicine(); + } else if (e.getKeyCode() == KeyEvent.VK_TAB && e.isShiftDown()) { + e.consume(); + eveningSpinner.requestFocusInWindow(); + } + } + }); + okButton.addActionListener(e -> { collectPrescriptionData(); @@ -513,72 +605,146 @@ public class MedicineDialog extends JDialog { }); } - private void filterMedicineTable() { + private void showOrUpdateSuggestions() { + if (isSelectionLocked) { + return; + } String filterText = medicineNameField.getText().trim(); - if (medicineData == null) return; + if (medicines == null || medicines.isEmpty()) { + return; + } + if (filterText.isEmpty()) { - tableModel.setDataVector(medicineData, medcineColumns); - resizeMedicineTableColumns(); - medicineTable.clearSelection(); - } else { - List filteredData = new ArrayList<>(); - List filteredMedicines = new ArrayList<>(); - String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); - - for (Medicine med : medicines) { - if (TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText)) { - filteredMedicines.add(med); - filteredData.add(med.toStringArray()); - } - } - - tableModel.setDataVector(filteredData.toArray(new String[0][0]), medcineColumns); - resizeMedicineTableColumns(); - if (!filteredData.isEmpty()) { - medicineTable.setRowSelectionInterval(0,0); - handleMedicineTableRowSelection(0); - } else { - clearInputFields(); + suggestionPopup.setVisible(false); + return; + } + + filteredMedicines.clear(); + List filteredDisplayData = new ArrayList<>(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); + + for (Medicine med : medicines) { + if (TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText)) { + filteredMedicines.add(med); + filteredDisplayData.add(new String[]{ + med.getName(), + med.getQuantity(), + med.getUnit(), + med.getSellingPrice() + }); } } - } - private void resizeMedicineTableColumns() { - columnModel = medicineTable.getColumnModel(); - columnModel.getColumn(0).setPreferredWidth(20); - columnModel.getColumn(1).setPreferredWidth(150); - columnModel.getColumn(2).setPreferredWidth(100); - columnModel.getColumn(3).setPreferredWidth(200); - columnModel.getColumn(4).setPreferredWidth(40); - columnModel.getColumn(5).setPreferredWidth(40); - columnModel.getColumn(6).setPreferredWidth(50); + + suggestionTableModel.setDataVector(filteredDisplayData.toArray(new String[0][0]), suggestionColumns); + resizeSuggestionTableColumns(); + + if (!filteredDisplayData.isEmpty()) { + suggestionTable.setRowSelectionInterval(0, 0); + + // Set preferred size for the popup's scroll pane + JScrollPane scrollPane = (JScrollPane) suggestionPopup.getComponent(0); + int headerHeight = suggestionTable.getTableHeader().getPreferredSize().height; + int rowsHeight = suggestionTable.getRowCount() * suggestionTable.getRowHeight(); + int height = Math.min(rowsHeight, 200) + headerHeight; + int width = medicineNameField.getWidth(); + scrollPane.setPreferredSize(new Dimension(width, height)); + + suggestionPopup.pack(); + suggestionPopup.show(medicineNameField, 0, medicineNameField.getHeight()); + medicineNameField.requestFocusInWindow(); + } else { + suggestionPopup.setVisible(false); + // clearInputFields(); // Don't clear fields if the user is typing + } } + private void resizeSuggestionTableColumns() { + columnModel = suggestionTable.getColumnModel(); + columnModel.getColumn(0).setPreferredWidth(200); // Tên thuốc + columnModel.getColumn(1).setPreferredWidth(50); // Tồn kho + columnModel.getColumn(2).setPreferredWidth(50); // ĐVT + columnModel.getColumn(3).setPreferredWidth(80); // Giá + } + private void handleMedicineTableRowSelection(int viewRow) { - if (viewRow < 0 || viewRow >= medicineTable.getRowCount()) { + if (viewRow < 0 || viewRow >= filteredMedicines.size()) { + clearInputFields(); + return; + } + Medicine selectedMedicine = filteredMedicines.get(viewRow); + populateFieldsFromMedicine(selectedMedicine); + } + + private void handleExactMatch() { + String searchName = medicineNameField.getText().trim(); + if (searchName.isEmpty()) { + return; + } + + Medicine foundMedicine = null; + String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); + + for (Medicine med : medicines) { + if (TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { + foundMedicine = med; + break; + } + } + + if (foundMedicine != null) { + populateFieldsFromMedicine(foundMedicine); + quantitySpinner.requestFocusInWindow(); + } + } + + private void populateFieldsFromMedicine(Medicine medicine) { + if (medicine == null) { clearInputFields(); return; } - int modelRow = medicineTable.convertRowIndexToModel(viewRow); isProgrammaticallySettingMedicineNameField = true; - medicineNameField.setText(tableModel.getValueAt(modelRow, 1).toString()); + medicineNameField.setText(medicine.getName()); isProgrammaticallySettingMedicineNameField = false; - medicineCompanyField.setText(tableModel.getValueAt(modelRow, 2).toString()); - medicineDescriptionField.setText(tableModel.getValueAt(modelRow, 3).toString()); - quantityLeftField.setText(tableModel.getValueAt(modelRow, 4).toString()); - UnitComboBox.setSelectedItem(tableModel.getValueAt(modelRow, 5).toString()); - priceField.setText(tableModel.getValueAt(modelRow, 6).toString()); - + medicineCompanyField.setText(medicine.getCompany()); + medicineDescriptionField.setText(medicine.getDescription()); + quantityLeftField.setText(medicine.getQuantity()); + UnitComboBox.setSelectedItem(medicine.getUnit()); + priceField.setText(medicine.getSellingPrice()); + quantitySpinner.setValue(1); - morningSpinner.setValue(0); - noonSpinner.setValue(0); - eveningSpinner.setValue(0); - noteField.setText(""); + + String preferenceNote = medicine.getPreferenceNote(); + if (preferenceNote != null && !preferenceNote.trim().isEmpty()) { + String[] parts = preferenceNote.split(",", 4); + try { + morningSpinner.setValue(parts.length > 0 ? Integer.parseInt(parts[0].trim()) : 0); + noonSpinner.setValue(parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 0); + eveningSpinner.setValue(parts.length > 2 ? Integer.parseInt(parts[2].trim()) : 0); + noteField.setText(parts.length > 3 ? parts[3].trim() : ""); + } catch (NumberFormatException e) { + logger.error("Could not parse preference note: {}", preferenceNote, e); + // Reset to default values in case of parsing error + morningSpinner.setValue(0); + noonSpinner.setValue(0); + eveningSpinner.setValue(0); + noteField.setText(""); + } + } else { + // No preference note, set default values + morningSpinner.setValue(0); + noonSpinner.setValue(0); + eveningSpinner.setValue(0); + noteField.setText(""); + } + updateTotalField(); + isSelectionLocked = true; } private void clearInputFields() { + // Don't clear the name field as the user is typing in it medicineCompanyField.setText(""); medicineDescriptionField.setText(""); quantityLeftField.setText(""); @@ -605,33 +771,37 @@ public class MedicineDialog extends JDialog { } private void addSelectedMedicine() { - int selectedRowInMedicineTable = medicineTable.getSelectedRow(); - if (selectedRowInMedicineTable == -1 && medicineNameField.getText().isEmpty()) { - JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ danh sách hoặc tìm kiếm.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); + int selectedRowInSuggestionTable = suggestionTable.getSelectedRow(); + + String medicineId, name, unit, currentPrice; + String searchName = medicineNameField.getText().trim(); + + if (searchName.isEmpty()) { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); return; } - String medicineId, name, unit, currentPrice; - - if (selectedRowInMedicineTable != -1) { - int modelRow = medicineTable.convertRowIndexToModel(selectedRowInMedicineTable); - medicineId = tableModel.getValueAt(modelRow, 0).toString(); - name = tableModel.getValueAt(modelRow, 1).toString(); - unit = tableModel.getValueAt(modelRow, 5).toString(); - currentPrice = tableModel.getValueAt(modelRow, 6).toString(); + // Prioritize selection from the popup table if it's open and has a selection + if (suggestionPopup.isVisible() && selectedRowInSuggestionTable != -1) { + Medicine selectedMedicine = filteredMedicines.get(suggestionTable.convertRowIndexToModel(selectedRowInSuggestionTable)); + medicineId = selectedMedicine.getId(); + name = selectedMedicine.getName(); + unit = selectedMedicine.getUnit(); + currentPrice = selectedMedicine.getSellingPrice(); } else { - String searchName = medicineNameField.getText().trim(); + // Otherwise, try to find an exact match from the text field Medicine foundMedicine = null; - + String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); + for (Medicine med : medicines) { - if (TextUtils.removeAccents(med.getName().toLowerCase()).equals(TextUtils.removeAccents(searchName.toLowerCase()))) { + if (TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { foundMedicine = med; break; } } if (foundMedicine == null) { - JOptionPane.showMessageDialog(this, "Không tìm thấy ID cho thuốc: " + searchName + ". Vui lòng chọn từ bảng.", "Lỗi", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, "Không tìm thấy thuốc khớp với: '" + searchName + "'. Vui lòng chọn từ danh sách gợi ý.", "Lỗi", JOptionPane.ERROR_MESSAGE); return; } medicineId = foundMedicine.getId(); @@ -652,10 +822,17 @@ public class MedicineDialog extends JDialog { return; } selectedTableModel.addRow(new Object[]{medicineId, name, quantity, unit, morningVal, noonVal, eveningVal, currentPrice, totalAmount, note}); + + // Clear fields for next entry + isProgrammaticallySettingMedicineNameField = true; + medicineNameField.setText(""); + isProgrammaticallySettingMedicineNameField = false; + clearInputFields(); + medicineNameField.requestFocusInWindow(); } private void removeSelectedMedicine() { - int selectedRow = selectedTable.getSelectedRow(); + int selectedRow = chosenMedicineTable.getSelectedRow(); if (selectedRow != -1) { selectedTableModel.removeRow(selectedRow); } else { diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/ServiceDialog/ServiceDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/ServiceDialog/ServiceDialog.java index 44ed949..1a5939b 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/ServiceDialog/ServiceDialog.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/ServiceDialog/ServiceDialog.java @@ -24,6 +24,9 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,11 +43,15 @@ public class ServiceDialog extends JDialog { private String[][] serviceData; private final ResponseListener getSerInfoResponseListener = this::getSerInfoHandler; private TableColumnModel columnModel; - private JTable serviceTable; - private JTable selectedTable; + private JTable chosenServiceTable; + private JTable suggestionTable; + private DefaultTableModel suggestionTableModel; + private JPopupMenu suggestionPopup; private boolean isProgrammaticallySettingNameField = false; + private boolean isSelectionLocked = false; private List services = new ArrayList<>(); + private List filteredServices = new ArrayList<>(); private String[][] servicePrescription; private String[][] originalPrescription; // Store original data for comparison private static final Logger logger = LoggerFactory.getLogger(ServiceDialog.class); @@ -60,9 +67,7 @@ public class ServiceDialog extends JDialog { // Get Service objects from the response services = response.getServices(); - // Set the data vector with the raw string arrays for backward compatibility - tableModel.setDataVector(serviceData, serviceColumns); - resizeServiceTableColumns(); + // We don't need to populate a main table anymore, just have the data ready. } void sendGetSerInfoRequest() { @@ -91,10 +96,16 @@ public class ServiceDialog extends JDialog { private void initUI(Frame parent) { setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(1100, 600); + setSize(1200, 700); 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); + // 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 @@ -134,7 +145,7 @@ public class ServiceDialog extends JDialog { serviceInfoPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Thông tin dịch vụ", 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; @@ -144,13 +155,15 @@ public class ServiceDialog extends JDialog { notePanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Ghi 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; mainInputPanel.add(notePanel, mainGbc); JPanel addServiceButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JButton addServiceButton = new JButton("Thêm dịch vụ"); + JButton addServiceButton = new JButton("Thêm dịch vụ (F1)"); + addServiceButton.setFont(labelFont); + addServiceButton.setPreferredSize(new Dimension(220, 40)); addServiceButtonPanel.add(addServiceButton); mainGbc.gridy = 2; mainInputPanel.add(addServiceButtonPanel, mainGbc); @@ -168,45 +181,60 @@ public class ServiceDialog extends JDialog { gbc.gridy = 0; gbc.gridx = 0; - serviceInfoPanel.add(new JLabel("Tên dịch vụ:"), gbc); + JLabel nameLabel = new JLabel("Tên dịch vụ:"); + nameLabel.setFont(labelFont); + serviceInfoPanel.add(nameLabel, gbc); gbc.gridx = 1; gbc.gridwidth = 3; gbc.weightx = 1.0; serviceNameField = new JTextField(20); + serviceNameField.setFont(textFont); + serviceNameField.setPreferredSize(textFieldSize); serviceInfoPanel.add(serviceNameField, gbc); gbc.gridwidth = 1; gbc.weightx = 0.0; gbc.gridy++; gbc.gridx = 0; - serviceInfoPanel.add(new JLabel("Số lượng:"), gbc); + JLabel quantityLabel = new JLabel("Số lượng:"); + quantityLabel.setFont(labelFont); + serviceInfoPanel.add(quantityLabel, gbc); gbc.gridx = 1; gbc.weightx = 0.5; quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1)); - quantitySpinner.setPreferredSize(new Dimension(80, 25)); + quantitySpinner.setFont(textFont); + quantitySpinner.setPreferredSize(new Dimension(80, 30)); serviceInfoPanel.add(quantitySpinner, gbc); gbc.weightx = 0.0; gbc.gridx = 2; - serviceInfoPanel.add(new JLabel("Đơn giá (VNĐ):"), gbc); + JLabel priceLabel = new JLabel("Đơn giá (VNĐ):"); + priceLabel.setFont(labelFont); + serviceInfoPanel.add(priceLabel, gbc); gbc.gridx = 3; gbc.weightx = 0.5; priceField = new JTextField(10); + priceField.setFont(textFont); + priceField.setPreferredSize(textFieldSize); priceField.setEditable(false); serviceInfoPanel.add(priceField, gbc); gbc.weightx = 0.0; gbc.gridy++; gbc.gridx = 0; - serviceInfoPanel.add(new JLabel("Thành tiền (VNĐ):"), gbc); + JLabel totalLabel = new JLabel("Thành tiền (VNĐ):"); + totalLabel.setFont(labelFont); + serviceInfoPanel.add(totalLabel, gbc); gbc.gridx = 1; gbc.gridwidth = 3; gbc.weightx = 1.0; totalField = new JTextField(10); + totalField.setFont(textFont); + totalField.setPreferredSize(textFieldSize); totalField.setEditable(false); serviceInfoPanel.add(totalField, gbc); gbc.gridwidth = 1; @@ -215,7 +243,9 @@ public class ServiceDialog extends JDialog { gbc.gridy = 0; gbc.gridx = 0; gbc.anchor = GridBagConstraints.NORTHWEST; - notePanel.add(new JLabel("Ghi chú:"), gbc); + JLabel noteLabel = new JLabel("Ghi chú:"); + noteLabel.setFont(labelFont); + notePanel.add(noteLabel, gbc); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 1; @@ -223,6 +253,7 @@ public class ServiceDialog extends JDialog { gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; noteField = new JTextArea(3, 20); + noteField.setFont(textFont); noteField.setLineWrap(true); noteField.setWrapStyleWord(true); JScrollPane noteScrollPane = new JScrollPane(noteField); @@ -238,13 +269,31 @@ public class ServiceDialog extends JDialog { return false; } }; - serviceTable = new JTable(tableModel); - serviceTable.setFont(new Font("Arial", Font.PLAIN, 14)); - serviceTable.setRowHeight(25); - serviceTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - serviceTable.setAutoCreateRowSorter(true); - JScrollPane serviceTableScrollPane = new JScrollPane(serviceTable); - serviceTableScrollPane.setPreferredSize(new Dimension(550, 250)); + // serviceTable = new JTable(tableModel); // This line is removed + // serviceTable.setFont(new Font("Arial", Font.PLAIN, 14)); // This line is removed + // serviceTable.setRowHeight(25); // This line is removed + // serviceTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // This line is removed + // serviceTable.setAutoCreateRowSorter(true); // This line is removed + // JScrollPane serviceTableScrollPane = new JScrollPane(serviceTable); // This line is removed + // serviceTableScrollPane.setPreferredSize(new Dimension(550, 250)); // This line is removed + + suggestionTableModel = new DefaultTableModel(new String[0][0], serviceColumns) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }; + suggestionTable = new JTable(suggestionTableModel); + suggestionTable.setFont(textFont); + suggestionTable.setRowHeight(30); + suggestionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + suggestionTable.setAutoCreateRowSorter(false); // We filter manually + JScrollPane suggestionScrollPane = new JScrollPane(suggestionTable); + + suggestionPopup = new JPopupMenu(); + suggestionPopup.setLayout(new BorderLayout()); + suggestionPopup.add(suggestionScrollPane, BorderLayout.CENTER); + suggestionPopup.setFocusable(false); String[] selectedColumnNames = {"ID", "Tên dịch vụ", "Số lượng", "Đơn giá", "Thành tiền", "Ghi chú"}; selectedTableModel = new DefaultTableModel(new String[][]{}, selectedColumnNames) { @@ -253,120 +302,134 @@ public class ServiceDialog extends JDialog { return column == 2 || column == 5; } }; - selectedTable = new JTable(selectedTableModel); - selectedTable.setFont(new Font("Arial", Font.PLAIN, 14)); - selectedTable.setRowHeight(25); - JScrollPane selectedTableScrollPane = new JScrollPane(selectedTable); + chosenServiceTable = new JTable(selectedTableModel); + chosenServiceTable.setFont(textFont); + chosenServiceTable.setRowHeight(30); + JScrollPane selectedTableScrollPane = new JScrollPane(chosenServiceTable); selectedTableScrollPane.setPreferredSize(new Dimension(550, 150)); JPanel removeServiceButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton removeServiceButton = new JButton("Xóa dịch vụ đã chọn"); + removeServiceButton.setFont(labelFont); removeServiceButtonPanel.add(removeServiceButton); - // Create titled panels for right side sections - JPanel availableServicesPanel = new JPanel(new BorderLayout()); - availableServicesPanel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createEtchedBorder(), "Dịch vụ có", - javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 14), new Color(63, 81, 181) - )); - availableServicesPanel.add(serviceTableScrollPane, BorderLayout.CENTER); - JPanel chosenServicesPanel = new JPanel(new BorderLayout()); chosenServicesPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Dịch vụ đã chọn", javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - new Font("Arial", Font.BOLD, 14), new Color(220, 20, 60) + titleFont, new Color(220, 20, 60) )); chosenServicesPanel.setBackground(new Color(255, 240, 240)); // Light red background selectedTableScrollPane.setBackground(new Color(255, 240, 240)); // Light red background chosenServicesPanel.add(selectedTableScrollPane, BorderLayout.CENTER); chosenServicesPanel.add(removeServiceButtonPanel, BorderLayout.SOUTH); - JPanel rightPanel = new JPanel(new BorderLayout(0,5)); - rightPanel.add(availableServicesPanel, BorderLayout.CENTER); - rightPanel.add(chosenServicesPanel, 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.4); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mainInputScrollPane, chosenServicesPanel); + splitPane.setResizeWeight(0.35); add(splitPane, BorderLayout.CENTER); JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okButton = new JButton("Lưu"); + okButton.setFont(labelFont); JButton cancelButton = new JButton("Hủy"); + cancelButton.setFont(labelFont); bottomButtonPanel.add(okButton); bottomButtonPanel.add(cancelButton); add(bottomButtonPanel, BorderLayout.SOUTH); + // F1 Key binding for adding service + Action addAction = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + addSelectedService(); + } + }; + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F1"), "addServiceAction"); + getRootPane().getActionMap().put("addServiceAction", addAction); + serviceNameField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { if (!isProgrammaticallySettingNameField) { - SwingUtilities.invokeLater(() -> filterServiceTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } public void removeUpdate(DocumentEvent e) { if (!isProgrammaticallySettingNameField) { - SwingUtilities.invokeLater(() -> filterServiceTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } public void insertUpdate(DocumentEvent e) { if (!isProgrammaticallySettingNameField) { - SwingUtilities.invokeLater(() -> filterServiceTable()); + isSelectionLocked = false; + SwingUtilities.invokeLater(() -> showOrUpdateSuggestions()); } } }); - serviceTable.addMouseListener(new MouseAdapter() { + suggestionTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1) { - SwingUtilities.invokeLater(() -> { - int selectedRow = serviceTable.getSelectedRow(); - if (selectedRow != -1) { - handleServiceTableRowSelection(selectedRow); - } - }); - } + SwingUtilities.invokeLater(() -> { + int selectedRow = suggestionTable.getSelectedRow(); + if (selectedRow == -1) return; + + handleServiceTableRowSelection(selectedRow); // Populate fields on any click + + if (e.getClickCount() == 2) { + addSelectedService(); + suggestionPopup.setVisible(false); + } + }); } }); serviceNameField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (serviceTable.getSelectedRow() != -1) { - addSelectedService(); + if (suggestionPopup.isVisible()) { + int selectedRow = suggestionTable.getSelectedRow(); + int rowCount = suggestionTable.getRowCount(); + + switch (e.getKeyCode()) { + case KeyEvent.VK_DOWN: + if (rowCount > 0) { + selectedRow = (selectedRow + 1) % rowCount; + suggestionTable.setRowSelectionInterval(selectedRow, selectedRow); + suggestionTable.scrollRectToVisible(suggestionTable.getCellRect(selectedRow, 0, true)); + } + e.consume(); + break; + case KeyEvent.VK_UP: + if (rowCount > 0) { + selectedRow = (selectedRow - 1 + rowCount) % rowCount; + suggestionTable.setRowSelectionInterval(selectedRow, selectedRow); + suggestionTable.scrollRectToVisible(suggestionTable.getCellRect(selectedRow, 0, true)); + } + e.consume(); + break; + case KeyEvent.VK_ENTER: + if (selectedRow != -1) { + handleServiceTableRowSelection(suggestionTable.getSelectedRow()); + suggestionPopup.setVisible(false); + quantitySpinner.requestFocusInWindow(); + } + e.consume(); + break; + case KeyEvent.VK_ESCAPE: + suggestionPopup.setVisible(false); + e.consume(); + break; } - } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { - if (serviceTable.getRowCount() > 0) { - int selectedRow = serviceTable.getSelectedRow(); - if (selectedRow < serviceTable.getRowCount() - 1) { - serviceTable.setRowSelectionInterval(selectedRow + 1, selectedRow + 1); - serviceTable.scrollRectToVisible(serviceTable.getCellRect(selectedRow + 1, 0, true)); - handleServiceTableRowSelection(selectedRow+1); - } else { - serviceTable.setRowSelectionInterval(0,0); - serviceTable.scrollRectToVisible(serviceTable.getCellRect(0,0,true)); - handleServiceTableRowSelection(0); - } - } - } else if (e.getKeyCode() == KeyEvent.VK_UP) { - if (serviceTable.getRowCount() > 0) { - int selectedRow = serviceTable.getSelectedRow(); - if (selectedRow > 0) { - serviceTable.setRowSelectionInterval(selectedRow - 1, selectedRow - 1); - serviceTable.scrollRectToVisible(serviceTable.getCellRect(selectedRow - 1, 0, true)); - handleServiceTableRowSelection(selectedRow - 1); - } else { - serviceTable.setRowSelectionInterval(serviceTable.getRowCount()-1, serviceTable.getRowCount()-1); - serviceTable.scrollRectToVisible(serviceTable.getCellRect(serviceTable.getRowCount()-1,0,true)); - handleServiceTableRowSelection(serviceTable.getRowCount()-1); - } + } else { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + handleExactMatch(); } } } @@ -377,6 +440,19 @@ public class ServiceDialog extends JDialog { addServiceButton.addActionListener(e -> addSelectedService()); removeServiceButton.addActionListener(e -> removeSelectedService()); + noteField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_TAB && e.getModifiersEx() == 0) { + e.consume(); + addSelectedService(); + } else if (e.getKeyCode() == KeyEvent.VK_TAB && e.isShiftDown()) { + e.consume(); + quantitySpinner.requestFocusInWindow(); + } + } + }); + okButton.addActionListener(e -> { collectPrescriptionData(); @@ -411,58 +487,107 @@ public class ServiceDialog extends JDialog { }); } - private void filterServiceTable() { + private void showOrUpdateSuggestions() { + if (isSelectionLocked) { + return; + } String filterText = serviceNameField.getText().trim(); - if (serviceData == null) return; + if (services == null || services.isEmpty()) { + return; + } + if (filterText.isEmpty()) { - tableModel.setDataVector(serviceData, serviceColumns); - resizeServiceTableColumns(); - serviceTable.clearSelection(); + suggestionPopup.setVisible(false); + return; + } + + filteredServices.clear(); + List filteredDisplayData = new ArrayList<>(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); + + for (Service service : services) { + if (TextUtils.removeAccents(service.getName().toLowerCase()).contains(lowerCaseFilterText)) { + filteredServices.add(service); + filteredDisplayData.add(service.toStringArray()); + } + } + + suggestionTableModel.setDataVector(filteredDisplayData.toArray(new String[0][0]), serviceColumns); + resizeSuggestionTableColumns(); + + if (!filteredDisplayData.isEmpty()) { + suggestionTable.setRowSelectionInterval(0, 0); + + // Set preferred size for the popup's scroll pane + JScrollPane scrollPane = (JScrollPane) suggestionPopup.getComponent(0); + int headerHeight = suggestionTable.getTableHeader().getPreferredSize().height; + int rowsHeight = suggestionTable.getRowCount() * suggestionTable.getRowHeight(); + int height = Math.min(rowsHeight, 200) + headerHeight; + int width = serviceNameField.getWidth(); + scrollPane.setPreferredSize(new Dimension(width, height)); + + suggestionPopup.pack(); + suggestionPopup.show(serviceNameField, 0, serviceNameField.getHeight()); + serviceNameField.requestFocusInWindow(); } else { - List filteredData = new ArrayList<>(); - List filteredServices = new ArrayList<>(); - String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); - - for (Service service : services) { - if (TextUtils.removeAccents(service.getName().toLowerCase()).contains(lowerCaseFilterText)) { - filteredServices.add(service); - filteredData.add(service.toStringArray()); - } - } - - tableModel.setDataVector(filteredData.toArray(new String[0][0]), serviceColumns); - resizeServiceTableColumns(); - if (!filteredData.isEmpty()) { - serviceTable.setRowSelectionInterval(0,0); - handleServiceTableRowSelection(0); - } else { - clearInputFields(); - } + suggestionPopup.setVisible(false); + clearInputFields(); } } - private void resizeServiceTableColumns() { - columnModel = serviceTable.getColumnModel(); + private void resizeSuggestionTableColumns() { + columnModel = suggestionTable.getColumnModel(); columnModel.getColumn(0).setPreferredWidth(30); columnModel.getColumn(1).setPreferredWidth(300); columnModel.getColumn(2).setPreferredWidth(100); } private void handleServiceTableRowSelection(int viewRow) { - if (viewRow < 0 || viewRow >= serviceTable.getRowCount()) { + if (viewRow < 0 || viewRow >= filteredServices.size()) { clearInputFields(); return; } - int modelRow = serviceTable.convertRowIndexToModel(viewRow); - + Service selectedService = filteredServices.get(viewRow); + populateFieldsFromService(selectedService); + } + + private void handleExactMatch() { + String searchName = serviceNameField.getText().trim(); + if (searchName.isEmpty()) { + return; + } + + Service foundService = null; + String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); + + for (Service service : services) { + if (TextUtils.removeAccents(service.getName().toLowerCase()).equals(normalizedSearchName)) { + foundService = service; + break; + } + } + + if (foundService != null) { + populateFieldsFromService(foundService); + quantitySpinner.requestFocusInWindow(); + } + } + + private void populateFieldsFromService(Service service) { + if (service == null) { + clearInputFields(); + return; + } + isProgrammaticallySettingNameField = true; - serviceNameField.setText(tableModel.getValueAt(modelRow, 1).toString()); + serviceNameField.setText(service.getName()); isProgrammaticallySettingNameField = false; - - priceField.setText(tableModel.getValueAt(modelRow, 2).toString()); + + priceField.setText(service.getCost()); quantitySpinner.setValue(1); noteField.setText(""); updateTotalField(); + isSelectionLocked = true; } private void clearInputFields(){ @@ -491,32 +616,34 @@ public class ServiceDialog extends JDialog { } private void addSelectedService() { - int selectedRowInServiceTable = serviceTable.getSelectedRow(); - if (selectedRowInServiceTable == -1 && serviceNameField.getText().isEmpty()) { - JOptionPane.showMessageDialog(this, "Vui lòng chọn một dịch vụ từ danh sách hoặc tìm kiếm.", "Chưa chọn dịch vụ", JOptionPane.WARNING_MESSAGE); + int selectedRowInSuggestionTable = suggestionTable.getSelectedRow(); + String searchName = serviceNameField.getText().trim(); + + if (searchName.isEmpty()) { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một dịch vụ.", "Chưa chọn dịch vụ", JOptionPane.WARNING_MESSAGE); return; } String serviceId, name, currentPrice; - if (selectedRowInServiceTable != -1) { - int modelRow = serviceTable.convertRowIndexToModel(selectedRowInServiceTable); - serviceId = tableModel.getValueAt(modelRow, 0).toString(); - name = tableModel.getValueAt(modelRow, 1).toString(); - currentPrice = tableModel.getValueAt(modelRow, 2).toString(); + if (suggestionPopup.isVisible() && selectedRowInSuggestionTable != -1) { + Service selectedService = filteredServices.get(suggestionTable.convertRowIndexToModel(selectedRowInSuggestionTable)); + serviceId = selectedService.getId(); + name = selectedService.getName(); + currentPrice = selectedService.getCost(); } else { - String searchName = serviceNameField.getText().trim(); Service foundService = null; - + String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); + for (Service service : services) { - if (TextUtils.removeAccents(service.getName().toLowerCase()).equals(TextUtils.removeAccents(searchName.toLowerCase()))) { + if (TextUtils.removeAccents(service.getName().toLowerCase()).equals(normalizedSearchName)) { foundService = service; break; } } if (foundService == null) { - JOptionPane.showMessageDialog(this, "Không tìm thấy ID cho dịch vụ: " + searchName + ". Vui lòng chọn từ bảng.", "Lỗi", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, "Không tìm thấy dịch vụ khớp với: '" + searchName + "'. Vui lòng chọn từ danh sách gợi ý.", "Lỗi", JOptionPane.ERROR_MESSAGE); return; } serviceId = foundService.getId(); @@ -540,14 +667,28 @@ public class ServiceDialog extends JDialog { double singlePrice = Double.parseDouble(selectedTableModel.getValueAt(i, 3).toString()); selectedTableModel.setValueAt(newQuantity, i, 2); selectedTableModel.setValueAt(String.format("%.0f", newQuantity * singlePrice), i, 4); + + // Clear fields for next entry + isProgrammaticallySettingNameField = true; + serviceNameField.setText(""); + isProgrammaticallySettingNameField = false; + clearInputFields(); + serviceNameField.requestFocusInWindow(); return; } } selectedTableModel.addRow(new Object[]{serviceId, name, quantity, currentPrice, totalAmount, note}); + + // Clear fields for next entry + isProgrammaticallySettingNameField = true; + serviceNameField.setText(""); + isProgrammaticallySettingNameField = false; + clearInputFields(); + serviceNameField.requestFocusInWindow(); } private void removeSelectedService() { - int selectedRow = selectedTable.getSelectedRow(); + int selectedRow = chosenServiceTable.getSelectedRow(); if (selectedRow != -1) { selectedTableModel.removeRow(selectedRow); } else { diff --git a/src/main/java/BsK/common/entity/Medicine.java b/src/main/java/BsK/common/entity/Medicine.java index bd6341f..b5b3e6d 100644 --- a/src/main/java/BsK/common/entity/Medicine.java +++ b/src/main/java/BsK/common/entity/Medicine.java @@ -18,14 +18,15 @@ public class Medicine { private String quantity; private String unit; private String sellingPrice; + private String preferenceNote; /** * Constructor to create Medicine from array data * @param data Array of medicine data from backend */ public Medicine(String[] data) { - if (data.length < 7) { - throw new IllegalArgumentException("Medicine data array must contain at least 7 elements"); + if (data.length < 8) { + throw new IllegalArgumentException("Medicine data array must contain at least 8 elements"); } this.id = data[0]; this.name = data[1]; @@ -34,6 +35,7 @@ public class Medicine { this.quantity = data[4]; this.unit = data[5]; this.sellingPrice = data[6]; + this.preferenceNote = data[7]; } /** @@ -41,6 +43,6 @@ public class Medicine { * @return String array representation of Medicine */ public String[] toStringArray() { - return new String[]{id, name, company, description, quantity, unit, sellingPrice}; + return new String[]{id, name, company, description, quantity, unit, sellingPrice, preferenceNote}; } } \ 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 4978bcd..dcfe2e3 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -347,7 +347,7 @@ public class ServerHandler extends SimpleChannelInboundHandler