[update] Revamp MedicineDialog and ServiceDialog to enhance user experience with improved UI elements, including keyboard shortcuts for adding items, dynamic suggestion popups, and refined input handling. Update data structures to accommodate new fields and streamline the selection process for medicines and services, ensuring better data management and user interaction.

This commit is contained in:
Lê Thành Đạt committed 2025-07-09 14:01:16 +07:00
1 parent e7af0cbf9d
commit f816ce64b2
5 files changed
+609 -288

No files matched your search

@@ -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<GetMedInfoResponse> getMedInfoResponseListener = this::getMedInfoHandler;
private TableColumnModel columnModel;
private HashMap<String, Boolean> 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<Medicine> medicines = new ArrayList<>();
private List<Medicine> 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("<html>Thêm thuốc <font color='red'>(F1)</font></html>");
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<String[]> filteredData = new ArrayList<>();
List<Medicine> 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<String[]> 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 {
@@ -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<GetSerInfoResponse> 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<Service> services = new ArrayList<>();
private List<Service> 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("<html>Thêm dịch vụ <font color='red'>(F1)</font></html>");
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<String[]> 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<String[]> filteredData = new ArrayList<>();
List<Service> 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 {
@@ -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};
}
}
@@ -347,7 +347,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
log.debug("Received GetMedInfoRequest");
try {
ResultSet rs = statement.executeQuery(
"select med_id, med_name, med_company, med_description, quantity, med_unit, med_selling_price\n" +
"select med_id, med_name, med_company, med_description, quantity, med_unit, med_selling_price, preference_note\n" +
" from Medicine"
);
@@ -363,17 +363,18 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String quantity = rs.getString("quantity");
String medUnit = rs.getString("med_unit");
String medSellingPrice = rs.getString("med_selling_price");
String preferenceNote = rs.getString("preference_note");
String result = String.join("|",medId, medName, medCompany, medDescription, quantity, medUnit,
medSellingPrice);
medSellingPrice, preferenceNote != null ? preferenceNote : "");
resultList.add(result);
}
String[] resultString = resultList.toArray(new String[0]);
String[][] resultArray = new String[resultString.length][];
for (int i = 0; i < resultString.length; i++) {
resultArray[i] = resultString[i].split("\\|");
resultArray[i] = resultString[i].split("\\|", -1);
}
UserUtil.sendPacket(currentUser.getSessionId(), new GetMedInfoResponse(resultArray));
Binary file not shown.