From 5bd321b28a82de50584b9608fbd4c0a54d2a32bd Mon Sep 17 00:00:00 2001
From: Dat Le
Date: Tue, 7 Jul 2026 23:22:30 -0400
Subject: [PATCH] Update patient search functionality in AddDialog to include
patient ID. Enhance GetRecentPatientRequest to support ID-based searches and
adjust ServerHandler to process ID queries. Revise CheckUpPage to
automatically mark checkups as finished based on settings. Update
configuration properties for application behavior.
---
config/config.properties | 4 +-
.../ui/component/CheckUpPage/CheckUpPage.java | 526 +++++++++++++++---
.../component/common/AddDialog/AddDialog.java | 92 ++-
.../packet/req/GetRecentPatientRequest.java | 10 +-
.../server/network/handler/ServerHandler.java | 23 +-
src/main/resources/log4j2.xml | 25 +-
temp/barcodes/invoice_barcode.png | Bin 97 -> 98 bytes
temp/qrcodes/invoice_qrcode.png | Bin 526 -> 512 bytes
8 files changed, 554 insertions(+), 126 deletions(-)
diff --git a/config/config.properties b/config/config.properties
index 714f407..f323dda 100644
--- a/config/config.properties
+++ b/config/config.properties
@@ -1,6 +1,6 @@
#BSK Clinic Management System Configuration - Updated via Settings Dialog
-#Tue Aug 05 09:52:46 ICT 2025
-app.auto_change_status_to_finished=false
+#Tue Jul 07 20:38:31 EDT 2026
+app.auto_change_status_to_finished=true
clinic.address=112B/1 Khu ph\u1ED1 2, x\u00E3 \u0110\u1EE9c H\u00F2a, t\u1EC9nh T\u00E2y Ninh
clinic.name=BS. NGUY\u1EC4N TH\u1ECA KH\u1EAEP
clinic.phone=0908410007
diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
index b57f0c8..956fab7 100644
--- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
+++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java
@@ -107,9 +107,11 @@ import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.Document;
+import javax.swing.text.BadLocationException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.io.Reader;
import java.io.StringReader;
// --- DateUtils Import ---
@@ -1133,8 +1135,6 @@ public class CheckUpPage extends JPanel {
add(centerContentPanel, BorderLayout.CENTER);
- // In the constructor, after creating notesField:
- setupNotesPasteHandler();
setupShortcuts();
// NOTE: CallPatientResponse is already handled by callPatientResponseListener (handleCallPatientResponse method)
@@ -1375,29 +1375,10 @@ public class CheckUpPage extends JPanel {
// Set preferred size for notes field
notesField.setPreferredSize(new Dimension(0, 400)); // Make it tall
- // Add UndoManager
+ // Undo/redo + copy/cut/paste key bindings
notesUndoManager = new UndoManager();
notesField.getDocument().addUndoableEditListener(notesUndoManager);
-
- notesField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Undo");
- notesField.getActionMap().put("Undo", new AbstractAction() {
- @Override
- public void actionPerformed(ActionEvent e) {
- if (notesUndoManager.canUndo()) {
- notesUndoManager.undo();
- }
- }
- });
-
- notesField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Redo");
- notesField.getActionMap().put("Redo", new AbstractAction() {
- @Override
- public void actionPerformed(ActionEvent e) {
- if (notesUndoManager.canRedo()) {
- notesUndoManager.redo();
- }
- }
- });
+ setupNotesEditorKeyBindings();
// Enhanced Toolbar for notes with more formatting options
@@ -1528,6 +1509,7 @@ public class CheckUpPage extends JPanel {
suggestionField.setFont(fieldFont);
suggestionField.setLineWrap(true);
suggestionField.setWrapStyleWord(true);
+ setupTextAreaEditorKeyBindings(suggestionField);
// addSelectAllOnFocus(suggestionField);
JScrollPane suggestionScrollPane = new JScrollPane(suggestionField);
suggestionScrollPane.setPreferredSize(new Dimension(0, 50)); // Reduced height
@@ -1614,6 +1596,7 @@ public class CheckUpPage extends JPanel {
diagnosisField.setFont(new Font("Arial", Font.BOLD, 16)); // Slightly smaller font
diagnosisField.setLineWrap(true);
diagnosisField.setWrapStyleWord(true);
+ setupTextAreaEditorKeyBindings(diagnosisField);
// addSelectAllOnFocus(diagnosisField);
diagnosisPanel.add(new JScrollPane(diagnosisField), BorderLayout.CENTER);
@@ -1630,6 +1613,7 @@ public class CheckUpPage extends JPanel {
conclusionField.setFont(new Font("Arial", Font.BOLD, 16)); // Slightly smaller font
conclusionField.setLineWrap(true);
conclusionField.setWrapStyleWord(true);
+ setupTextAreaEditorKeyBindings(conclusionField);
// addSelectAllOnFocus(conclusionField);
conclusionPanel.add(new JScrollPane(conclusionField), BorderLayout.CENTER);
@@ -2013,10 +1997,10 @@ public class CheckUpPage extends JPanel {
}
// Step 3: If report creation is successful, commit by saving and printing.
- // This will be turn on later
- // if ("ĐANG KHÁM".equals(statusComboBox.getSelectedItem())) {
- // statusComboBox.setSelectedItem("ĐÃ KHÁM");
- // }
+ // When enabled in Settings, automatically mark the checkup as done ("ĐÃ KHÁM") on print.
+ if (LocalStorage.autoChangeStatusToFinished) {
+ statusComboBox.setSelectedItem("ĐÃ KHÁM");
+ }
String statusToSavePrint = (String) statusComboBox.getSelectedItem();
handleSave();
@@ -4820,63 +4804,455 @@ public class CheckUpPage extends JPanel {
}
- private void handleRtfPaste() {
- try {
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- Transferable content = clipboard.getContents(null);
-
- if (content != null) {
- // Try to get RTF data first
- if (content.isDataFlavorSupported(new DataFlavor("text/rtf"))) {
- Object rtfData = content.getTransferData(new DataFlavor("text/rtf"));
- if (rtfData instanceof InputStream) {
- RTFEditorKit kit = (RTFEditorKit) notesField.getEditorKit();
- kit.read((InputStream) rtfData, notesField.getDocument(), notesField.getCaretPosition());
- return;
- }
- }
-
- // Fall back to plain text if RTF is not available
- if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
- String text = (String) content.getTransferData(DataFlavor.stringFlavor);
-
- // Convert plain text to RTF with proper Vietnamese encoding
- StringBuilder rtfContent = new StringBuilder();
- rtfContent.append("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\n");
- rtfContent.append("{\\colortbl ;\\red0\\green0\\blue0;}\n");
- rtfContent.append("\\viewkind4\\uc1\\pard\\cf1\\f0\\fs32 ");
-
- // Convert Vietnamese characters to RTF Unicode escape sequences
- for (char c : text.toCharArray()) {
- if (c < 128) {
- rtfContent.append(c);
- } else {
- rtfContent.append("\\u").append((int) c).append("?");
- }
- }
-
- rtfContent.append("\\par}");
-
- // Insert the RTF content
- ByteArrayInputStream in = new ByteArrayInputStream(rtfContent.toString().getBytes("ISO-8859-1"));
- RTFEditorKit kit = (RTFEditorKit) notesField.getEditorKit();
- kit.read(in, notesField.getDocument(), notesField.getCaretPosition());
- }
+ private void setupTextAreaEditorKeyBindings(JTextArea textArea) {
+ UndoManager undoManager = new UndoManager();
+ textArea.getDocument().addUndoableEditListener(undoManager);
+
+ int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
+ InputMap inputMap = textArea.getInputMap(JComponent.WHEN_FOCUSED);
+ ActionMap actionMap = textArea.getActionMap();
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, shortcutMask), "textArea-undo");
+ actionMap.put("textArea-undo", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ performUndo(undoManager);
}
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, shortcutMask), "textArea-redo");
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, shortcutMask | KeyEvent.SHIFT_DOWN_MASK), "textArea-redo");
+ actionMap.put("textArea-redo", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ performRedo(undoManager);
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "textArea-copy");
+ actionMap.put("textArea-copy", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleTextAreaCopy(textArea);
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, shortcutMask), "textArea-cut");
+ actionMap.put("textArea-cut", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleTextAreaCut(textArea);
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, shortcutMask), "textArea-paste");
+ actionMap.put("textArea-paste", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleTextAreaPaste(textArea);
+ }
+ });
+ }
+
+ private void handleTextAreaCopy(JTextArea textArea) {
+ int start = textArea.getSelectionStart();
+ int end = textArea.getSelectionEnd();
+ if (start == end) {
+ return;
+ }
+
+ try {
+ String selected = textArea.getSelectedText();
+ if (selected == null) {
+ selected = textArea.getDocument().getText(start, end - start);
+ }
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(new StringSelection(selected), null);
} catch (Exception e) {
- log.error("Error handling RTF paste", e);
+ log.warn("Custom text area copy failed, falling back to default", e);
+ textArea.copy();
}
}
- // Add paste key binding to the notes field
- private void setupNotesPasteHandler() {
- notesField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "customPaste");
- notesField.getActionMap().put("customPaste", new AbstractAction() {
+ private void handleTextAreaCut(JTextArea textArea) {
+ if (textArea.getSelectionStart() == textArea.getSelectionEnd()) {
+ return;
+ }
+ handleTextAreaCopy(textArea);
+ textArea.replaceSelection("");
+ }
+
+ private void handleTextAreaPaste(JTextArea textArea) {
+ try {
+ int insertPos = preparePasteInsertionPoint(textArea);
+
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable content = clipboard.getContents(null);
+ if (content == null) {
+ return;
+ }
+
+ if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ String text = (String) content.getTransferData(DataFlavor.stringFlavor);
+ if (text != null) {
+ insertPlainTextAtPosition(textArea, insertPos, text);
+ return;
+ }
+ }
+
+ if (content.isDataFlavorSupported(DataFlavor.allHtmlFlavor)) {
+ String html = (String) content.getTransferData(DataFlavor.allHtmlFlavor);
+ insertPlainTextAtPosition(textArea, insertPos, htmlToPlainText(html));
+ return;
+ }
+ } catch (Exception e) {
+ log.warn("Custom text area paste failed, falling back to default", e);
+ }
+
+ try {
+ int insertPos = preparePasteInsertionPoint(textArea);
+ textArea.setCaretPosition(insertPos);
+ textArea.paste();
+ } catch (Exception e) {
+ log.warn("Default text area paste failed", e);
+ }
+ }
+
+ /**
+ * Returns the document offset where pasted content should be inserted.
+ * Removes any current selection first and moves the caret to that point.
+ */
+ private int preparePasteInsertionPoint(JTextComponent component) throws BadLocationException {
+ if (!component.hasFocus()) {
+ component.requestFocusInWindow();
+ }
+
+ int start = component.getSelectionStart();
+ int end = component.getSelectionEnd();
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+
+ Document doc = component.getDocument();
+ if (start != end) {
+ doc.remove(start, end - start);
+ } else {
+ start = component.getCaretPosition();
+ }
+
+ component.setCaretPosition(start);
+ return start;
+ }
+
+ private void insertPlainTextAtPosition(JTextComponent component, int position, String text)
+ throws BadLocationException {
+ if (text == null || text.isEmpty()) {
+ return;
+ }
+ component.getDocument().insertString(position, text, null);
+ component.setCaretPosition(position + text.length());
+ }
+
+ private void performUndo(UndoManager undoManager) {
+ if (undoManager == null || !undoManager.canUndo()) {
+ return;
+ }
+ try {
+ undoManager.undo();
+ } catch (Exception e) {
+ log.warn("Undo failed", e);
+ }
+ }
+
+ private void performRedo(UndoManager undoManager) {
+ if (undoManager == null || !undoManager.canRedo()) {
+ return;
+ }
+ try {
+ undoManager.redo();
+ } catch (Exception e) {
+ log.warn("Redo failed", e);
+ }
+ }
+
+ private void setupNotesEditorKeyBindings() {
+ int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
+ InputMap inputMap = notesField.getInputMap(JComponent.WHEN_FOCUSED);
+ ActionMap actionMap = notesField.getActionMap();
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, shortcutMask), "notes-undo");
+ actionMap.put("notes-undo", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
- handleRtfPaste();
+ performNotesUndo();
}
});
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, shortcutMask), "notes-redo");
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, shortcutMask | KeyEvent.SHIFT_DOWN_MASK), "notes-redo");
+ actionMap.put("notes-redo", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ performNotesRedo();
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "notes-copy");
+ actionMap.put("notes-copy", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleNotesCopy();
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, shortcutMask), "notes-cut");
+ actionMap.put("notes-cut", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleNotesCut();
+ }
+ });
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, shortcutMask), "notes-paste");
+ actionMap.put("notes-paste", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleNotesPaste();
+ }
+ });
+ }
+
+ private void performNotesUndo() {
+ performUndo(notesUndoManager);
+ }
+
+ private void performNotesRedo() {
+ performRedo(notesUndoManager);
+ }
+
+ private void handleNotesCopy() {
+ int start = notesField.getSelectionStart();
+ int end = notesField.getSelectionEnd();
+ if (start == end) {
+ return;
+ }
+
+ try {
+ ByteArrayOutputStream rtfOut = new ByteArrayOutputStream();
+ notesField.getEditorKit().write(rtfOut, notesField.getDocument(), start, end - start);
+ byte[] rtfBytes = rtfOut.toByteArray();
+ String plainText = notesField.getSelectedText();
+ if (plainText == null) {
+ plainText = notesField.getDocument().getText(start, end - start);
+ }
+
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(new RtfTransferable(rtfBytes, plainText), null);
+ } catch (Exception e) {
+ log.warn("Custom notes copy failed, falling back to default", e);
+ notesField.copy();
+ }
+ }
+
+ private void handleNotesCut() {
+ if (notesField.getSelectionStart() == notesField.getSelectionEnd()) {
+ return;
+ }
+ handleNotesCopy();
+ notesField.replaceSelection("");
+ }
+
+ private void handleNotesPaste() {
+ try {
+ int insertPos = preparePasteInsertionPoint(notesField);
+
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable content = clipboard.getContents(null);
+ if (content == null) {
+ return;
+ }
+
+ if (tryPasteRtfAtPosition(content, insertPos)) {
+ return;
+ }
+
+ if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ String text = (String) content.getTransferData(DataFlavor.stringFlavor);
+ if (text != null) {
+ insertPlainTextAtPosition(notesField, insertPos, text);
+ return;
+ }
+ }
+
+ if (content.isDataFlavorSupported(DataFlavor.allHtmlFlavor)) {
+ String html = (String) content.getTransferData(DataFlavor.allHtmlFlavor);
+ insertPlainTextAtPosition(notesField, insertPos, htmlToPlainText(html));
+ return;
+ }
+ } catch (Exception e) {
+ log.warn("Custom notes paste failed, falling back to default", e);
+ }
+
+ try {
+ int insertPos = preparePasteInsertionPoint(notesField);
+ notesField.setCaretPosition(insertPos);
+ notesField.paste();
+ } catch (Exception e) {
+ log.warn("Default notes paste failed", e);
+ }
+ }
+
+ private boolean tryPasteRtfAtPosition(Transferable content, int insertPos) throws Exception {
+ byte[] rtfBytes = extractRtfBytes(content);
+ if (rtfBytes == null || rtfBytes.length == 0) {
+ return false;
+ }
+
+ // Read RTF into a temporary pane first, then insert the fragment at the caret.
+ // Reading a full RTF document directly into the main pane often appends at the end.
+ JEditorPane tempPane = new JEditorPane();
+ tempPane.setContentType("text/rtf");
+ tempPane.setEditorKit(new RTFEditorKit());
+ tempPane.read(new ByteArrayInputStream(rtfBytes), null);
+
+ Document tempDoc = tempPane.getDocument();
+ int tempLen = tempDoc.getLength();
+ if (tempLen <= 0) {
+ return false;
+ }
+
+ // DefaultStyledDocument always ends with a newline; exclude it from pasted fragment.
+ int fragmentLen = tempLen;
+ if (fragmentLen > 0 && "\n".equals(tempDoc.getText(fragmentLen - 1, 1))) {
+ fragmentLen--;
+ }
+ if (fragmentLen <= 0) {
+ return false;
+ }
+
+ ByteArrayOutputStream fragmentOut = new ByteArrayOutputStream();
+ tempPane.getEditorKit().write(fragmentOut, tempDoc, 0, fragmentLen);
+
+ notesField.setCaretPosition(insertPos);
+ RTFEditorKit kit = (RTFEditorKit) notesField.getEditorKit();
+ int lengthBefore = notesField.getDocument().getLength();
+ kit.read(new ByteArrayInputStream(fragmentOut.toByteArray()), notesField.getDocument(), insertPos);
+ int insertedLength = notesField.getDocument().getLength() - lengthBefore;
+ notesField.setCaretPosition(insertPos + insertedLength);
+ return true;
+ }
+
+ private byte[] extractRtfBytes(Transferable content) throws Exception {
+ DataFlavor[] flavors = content.getTransferDataFlavors();
+ for (DataFlavor flavor : flavors) {
+ if (flavor == null || !flavor.isMimeTypeEqual("text/rtf")) {
+ continue;
+ }
+
+ Object data = content.getTransferData(flavor);
+ if (data instanceof byte[]) {
+ return (byte[]) data;
+ }
+ if (data instanceof InputStream) {
+ return readAllBytes((InputStream) data);
+ }
+ if (data instanceof String) {
+ return ((String) data).getBytes("ISO-8859-1");
+ }
+ if (data instanceof Reader) {
+ char[] buffer = new char[4096];
+ StringBuilder sb = new StringBuilder();
+ Reader reader = (Reader) data;
+ int read;
+ while ((read = reader.read(buffer)) != -1) {
+ sb.append(buffer, 0, read);
+ }
+ return sb.toString().getBytes("ISO-8859-1");
+ }
+ }
+ return null;
+ }
+
+ private byte[] readAllBytes(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ return out.toByteArray();
+ }
+
+ private String htmlToPlainText(String html) {
+ if (html == null || html.isEmpty()) {
+ return "";
+ }
+ String text = html
+ .replaceAll("(?i)
", "\n")
+ .replaceAll("(?i)
", "\n")
+ .replaceAll("(?i)", "\n")
+ .replaceAll("<[^>]+>", "");
+ return text
+ .replace(" ", " ")
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\r\n", "\n")
+ .trim();
+ }
+
+ /** Clipboard payload that exposes both RTF and plain text for copy/paste interoperability. */
+ private static final class RtfTransferable implements Transferable {
+ private static final DataFlavor RTF_BYTES_FLAVOR;
+
+ static {
+ DataFlavor flavor = null;
+ try {
+ flavor = new DataFlavor("text/rtf;class=\"[B\"");
+ } catch (Exception ignored) {
+ // Fallback handled at runtime via supported flavors list.
+ }
+ RTF_BYTES_FLAVOR = flavor;
+ }
+
+ private final byte[] rtfBytes;
+ private final String plainText;
+
+ private RtfTransferable(byte[] rtfBytes, String plainText) {
+ this.rtfBytes = rtfBytes;
+ this.plainText = plainText != null ? plainText : "";
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ if (RTF_BYTES_FLAVOR != null) {
+ return new DataFlavor[]{RTF_BYTES_FLAVOR, DataFlavor.stringFlavor};
+ }
+ return new DataFlavor[]{DataFlavor.stringFlavor};
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ for (DataFlavor supported : getTransferDataFlavors()) {
+ if (supported.equals(flavor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
+ if (DataFlavor.stringFlavor.equals(flavor)) {
+ return plainText;
+ }
+ if (RTF_BYTES_FLAVOR != null && RTF_BYTES_FLAVOR.equals(flavor)) {
+ return rtfBytes;
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
}
/**
diff --git a/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java b/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java
index b02dfd6..ed4229c 100644
--- a/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java
+++ b/src/main/java/BsK/client/ui/component/common/AddDialog/AddDialog.java
@@ -46,6 +46,7 @@ public class AddDialog extends JDialog {
// --- Right Panel (Search & Table) ---
private JTextField searchNameField; // Dedicated search field
private JTextField searchPhoneField; // Dedicated search field
+ private JTextField searchIdField; // Dedicated search field for Mã BN (patient ID)
private DefaultTableModel patientTableModel;
private JTable patientTable;
private String[] patientColumns = {"Mã BN", "Tên Bệnh Nhân", "Năm sinh", "Số điện thoại" ,"Địa chỉ"};
@@ -79,6 +80,7 @@ public class AddDialog extends JDialog {
// --- Debounce Search ---
private Timer nameSearchTimer;
private Timer phoneSearchTimer;
+ private Timer idSearchTimer;
private void handleAddCheckupResponse(AddCheckupResponse response) {
log.info("Received AddCheckupResponse");
@@ -116,12 +118,24 @@ public class AddDialog extends JDialog {
}
private void sendGetRecentPatientRequest() {
- sendPatientSearchRequest(null, null, 1);
+ sendPatientSearchRequest(null, null, null, 1);
}
- private void sendPatientSearchRequest(String searchName, String searchPhone, int page) {
- log.info("Sending GetRecentPatientRequest with search: name='{}', phone='{}', page={}", searchName, searchPhone, page);
- NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecentPatientRequest(searchName, searchPhone, page, pageSize));
+ private void sendPatientSearchRequest(String searchName, String searchPhone, String searchId, int page) {
+ log.info("Sending GetRecentPatientRequest with search: name='{}', phone='{}', id='{}', page={}", searchName, searchPhone, searchId, page);
+ NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecentPatientRequest(searchName, searchPhone, searchId, page, pageSize));
+ }
+
+ // Reads the current search fields and issues a search request for the given page.
+ private void triggerSearch(int page) {
+ String searchName = searchNameField.getText().trim();
+ String searchPhone = searchPhoneField.getText().trim();
+ String searchId = searchIdField.getText().trim();
+ sendPatientSearchRequest(
+ searchName.isEmpty() ? null : searchName,
+ searchPhone.isEmpty() ? null : searchPhone,
+ searchId.isEmpty() ? null : searchId,
+ page);
}
private void getRecentPatientHandler(GetRecentPatientResponse response) {
@@ -190,7 +204,7 @@ public class AddDialog extends JDialog {
// Search on page 1 if we're not already there
if (currentPage != 1) {
log.info("Patient not found on current page, searching on page 1");
- sendPatientSearchRequest(null, null, 1);
+ sendPatientSearchRequest(null, null, null, 1);
return; // Don't reset autoSelectPatientId yet, let the next response handle it
} else {
log.warn("Could not find newly added patient with ID: {}", autoSelectPatientId);
@@ -244,6 +258,9 @@ public class AddDialog extends JDialog {
if (phoneSearchTimer != null && phoneSearchTimer.isRunning()) {
phoneSearchTimer.stop();
}
+ if (idSearchTimer != null && idSearchTimer.isRunning()) {
+ idSearchTimer.stop();
+ }
super.dispose();
}
@@ -255,7 +272,7 @@ public class AddDialog extends JDialog {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// Set size of the dialog
- setSize(1200, 800);
+ setSize(1450, 820);
// Put in the middle of parent window
setLocationRelativeTo(parent);
setResizable(true);
@@ -739,6 +756,9 @@ public class AddDialog extends JDialog {
inputScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
inputScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Usually not needed for forms
inputScrollPane.setBorder(BorderFactory.createEmptyBorder()); // Remove scroll pane's own border
+ // Enforce a usable width for the form so the split pane can't squeeze it when the
+ // right side (search bar) needs more room, and so the bold labels aren't clipped.
+ inputScrollPane.setMinimumSize(new Dimension(560, 0));
// Create right panel for table and pagination
JPanel rightPanel = new JPanel(new BorderLayout());
@@ -767,9 +787,10 @@ public class AddDialog extends JDialog {
rightPanel.add(paginationPanel, BorderLayout.SOUTH);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inputScrollPane, rightPanel);
- // Favor the right panel; set an initial divider position and resize weight
- splitPane.setDividerLocation(0.25);
- splitPane.setResizeWeight(0.2);
+ // Keep the form at a fixed, readable width (wide enough for the bold labels)
+ // and give all extra space to the table/search side.
+ splitPane.setDividerLocation(680);
+ splitPane.setResizeWeight(0.0);
add(splitPane, BorderLayout.CENTER);
@@ -880,6 +901,7 @@ public class AddDialog extends JDialog {
dobPicker.getJFormattedTextField().setText("");
// Clear right-side search fields
+ searchIdField.setText("");
searchNameField.setText("");
searchPhoneField.setText("");
@@ -902,12 +924,7 @@ public class AddDialog extends JDialog {
int selectedPage = (Integer) pageSpinner.getValue();
if (selectedPage != currentPage && selectedPage >= 1 && selectedPage <= totalPages) {
// Determine current search parameters from the dedicated search fields
- String searchName = searchNameField.getText().trim();
- String searchPhone = searchPhoneField.getText().trim();
- searchName = searchName.isEmpty() ? null : searchName;
- searchPhone = searchPhone.isEmpty() ? null : searchPhone;
-
- sendPatientSearchRequest(searchName, searchPhone, selectedPage);
+ triggerSearch(selectedPage);
}
});
setDefaultLocation();
@@ -915,20 +932,15 @@ public class AddDialog extends JDialog {
private void setupDebounceSearch() {
// Initialize timers for debounce search (1 second delay)
- nameSearchTimer = new Timer(1000, e -> {
- String searchName = searchNameField.getText().trim();
- String searchPhone = searchPhoneField.getText().trim();
- sendPatientSearchRequest(searchName.isEmpty() ? null : searchName, searchPhone.isEmpty() ? null : searchPhone, 1);
- });
+ nameSearchTimer = new Timer(1000, e -> triggerSearch(1));
nameSearchTimer.setRepeats(false);
- phoneSearchTimer = new Timer(1000, e -> {
- String searchName = searchNameField.getText().trim();
- String searchPhone = searchPhoneField.getText().trim();
- sendPatientSearchRequest(searchName.isEmpty() ? null : searchName, searchPhone.isEmpty() ? null : searchPhone, 1);
- });
+ phoneSearchTimer = new Timer(1000, e -> triggerSearch(1));
phoneSearchTimer.setRepeats(false);
+ idSearchTimer = new Timer(1000, e -> triggerSearch(1));
+ idSearchTimer.setRepeats(false);
+
// Add DocumentListener to searchNameField for debounced search
searchNameField.getDocument().addDocumentListener(new DocumentListener() {
@Override
@@ -965,6 +977,24 @@ private void setupDebounceSearch() {
}
});
+ // Add DocumentListener to searchIdField for debounced search
+ searchIdField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ resetAndStartTimer(idSearchTimer);
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ resetAndStartTimer(idSearchTimer);
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ resetAndStartTimer(idSearchTimer);
+ }
+ });
+
patientPhoneField.getDocument().addDocumentListener(new DocumentListener() {
private void syncPhoneFields() {
@@ -996,20 +1026,26 @@ private void setupDebounceSearch() {
}
private JPanel createSearchPanel() {
- JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 4));
searchPanel.setBorder(BorderFactory.createTitledBorder("Tìm kiếm bệnh nhân"));
+ searchPanel.add(new JLabel("Mã BN:"));
+ searchIdField = new JTextField(5);
+ searchIdField.setToolTipText("Tìm kiếm theo mã bệnh nhân");
+ searchPanel.add(searchIdField);
+
searchPanel.add(new JLabel("Họ tên:"));
- searchNameField = new JTextField(15);
+ searchNameField = new JTextField(12);
searchNameField.setToolTipText("Tìm kiếm theo họ tên (không cần dấu)");
searchPanel.add(searchNameField);
searchPanel.add(new JLabel("SĐT:"));
- searchPhoneField = new JTextField(10);
+ searchPhoneField = new JTextField(9);
searchPanel.add(searchPhoneField);
JButton clearSearchButton = new JButton("Xóa tìm kiếm");
clearSearchButton.addActionListener(e -> {
+ searchIdField.setText("");
searchNameField.setText("");
searchPhoneField.setText("");
sendGetRecentPatientRequest(); // Fetch initial list
diff --git a/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java b/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java
index dc6d55a..a25107c 100644
--- a/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java
+++ b/src/main/java/BsK/common/packet/req/GetRecentPatientRequest.java
@@ -13,16 +13,22 @@ import lombok.extern.slf4j.Slf4j;
public class GetRecentPatientRequest implements Packet {
private String searchName;
private String searchPhone;
+ private String searchId;
private int page = 1;
private int pageSize = 20;
// Constructor for backward compatibility (gets first page with default page size)
public GetRecentPatientRequest(int page, int pageSize) {
- this(null, null, page, pageSize);
+ this(null, null, null, page, pageSize);
}
// Constructor for search without pagination
public GetRecentPatientRequest(String searchName, String searchPhone) {
- this(searchName, searchPhone, 1, 20);
+ this(searchName, searchPhone, null, 1, 20);
+ }
+
+ // Constructor for name/phone search with pagination (no ID search)
+ public GetRecentPatientRequest(String searchName, String searchPhone, int page, int pageSize) {
+ this(searchName, searchPhone, null, page, pageSize);
}
}
diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java
index d0f4369..b34b7a4 100644
--- a/src/main/java/BsK/server/network/handler/ServerHandler.java
+++ b/src/main/java/BsK/server/network/handler/ServerHandler.java
@@ -380,6 +380,7 @@ public class ServerHandler extends SimpleChannelInboundHandler
+ filePattern="${logDir}/bsk_server-%d{yyyy-MM-dd}-%i.log.gz"
+ createOnDemand="true">
@@ -32,7 +33,8 @@
+ filePattern="${logDir}/bsk_server_error-%d{yyyy-MM-dd}-%i.log.gz"
+ createOnDemand="true">
@@ -50,7 +52,8 @@
+ filePattern="${logDir}/bsk_client-%d{yyyy-MM-dd}-%i.log.gz"
+ createOnDemand="true">
@@ -66,7 +69,8 @@
+ filePattern="${logDir}/bsk_client_error-%d{yyyy-MM-dd}-%i.log.gz"
+ createOnDemand="true">
@@ -81,29 +85,20 @@
-
-
-
-
-
-
-
-
+
-
+
diff --git a/temp/barcodes/invoice_barcode.png b/temp/barcodes/invoice_barcode.png
index 889707139a08d76c93688e727117086210881702..9de72aa4ad4f811d63e722643e3c96cd16260c7b 100644
GIT binary patch
delta 67
zcmYdHnxJB*>FMGaQW2N@gP)22`Nr+$=T_JJP~Q0WA^*v;4HAb}oPofO$&3tEAJ?9+
T{@Q(x0SG)@{an^LB{Ts5DXbq?
delta 66
zcmYdFoS;pyTSQW2N@gP)22`Nr+$=O#~{JYjOp59dv?I>!vOj3Mw*3d5@->a%(_
R?Y_VO1fH&bF6*2UngDiG8Vmpc
diff --git a/temp/qrcodes/invoice_qrcode.png b/temp/qrcodes/invoice_qrcode.png
index 2f5aa8fbbe013db57dae6fba1db9db1c7f25c62f..718e2e8c433d8cb4253af4c5f14178cf9efa2657 100644
GIT binary patch
delta 486
zcmV|1b_sPB!9~
zMPHU%jm5zQ?PzFh$V)Q=%N3V+SkffP)NP*u73z!KfgBG_`F~^{XFiA5_Ju$Rb(6A{{)sEL
zYLAD7ngn>C(#%}
zA&VVQM*>xRp=)m1$pGl1xD(X)x@ov0Am#Bb>0F^5=wFW14mlKZj`|MAD%m@OQ0SL7
z`cq#|80hH(pl-B^n}3J#kPm+Ghx~Knnh}uGp;|bp-5bW?5Z|{*g~s|LNhEwXPG~3e
caYByy3p|pInN5(ZMF0Q*07*qoM6N<$g5n42Bme*a
delta 500
zcmVx}1L6}*oFr#XZzi2xK0P2*i6c_s;5Jedy1wE9
zaZP*!TKO!TlOPPP&<0vdDpfN>HTefWXqFXaCDr_tD{W7R@Y)l$Pwk8}LLEFIek%`c
zIM$kAa$<6Yc4&h%wa&}n6aU~05oj{MRoNk07sx|fJ4N!&6{n1y+JqX76P^{t~
qt