From 46a1cf2dcc1caad3714d7104b1d1c83801178752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:10:41 -0500 Subject: [PATCH] [update] Enhance DataDialog and NavBar components to support doctor management features, including adding and editing doctors. Introduce client and server version display in the UI. Update GetCheckupDataRequest and LoginRequest to include shift information. Modify ultrasound result report for improved layout and clarity. Update binary database files to reflect changes. --- .vscode/settings.json | 3 +- database/BSK.db-shm | Bin 32768 -> 32768 bytes database/BSK.db-wal | Bin 3942872 -> 4082952 bytes .../ui/component/DataDialog/DataDialog.java | 17 +- .../DataDialog/DoctorManagementPanel.java | 338 ++++++++++++++++++ .../client/ui/component/common/NavBar.java | 28 ++ src/main/java/BsK/common/entity/Doctor.java | 49 +++ .../java/BsK/common/entity/DoctorItem.java | 2 +- .../common/packet/req/AddDoctorRequest.java | 13 + .../common/packet/req/EditDoctorRequest.java | 14 + .../packet/req/GetCheckupDataRequest.java | 1 + .../packet/req/GetDoctorInfoRequest.java | 11 + .../BsK/common/packet/req/LoginRequest.java | 1 + .../packet/res/GetDoctorInfoResponse.java | 50 +++ src/main/java/BsK/server/ServerDashboard.java | 26 +- .../server/network/handler/ServerHandler.java | 111 +++++- .../print_forms/ultrasoundresult.jrxml | 14 +- .../ultrasoundresult_potrait.jrxml | 14 +- 18 files changed, 665 insertions(+), 27 deletions(-) create mode 100644 src/main/java/BsK/client/ui/component/DataDialog/DoctorManagementPanel.java create mode 100644 src/main/java/BsK/common/entity/Doctor.java create mode 100644 src/main/java/BsK/common/packet/req/AddDoctorRequest.java create mode 100644 src/main/java/BsK/common/packet/req/EditDoctorRequest.java create mode 100644 src/main/java/BsK/common/packet/req/GetDoctorInfoRequest.java create mode 100644 src/main/java/BsK/common/packet/res/GetDoctorInfoResponse.java diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bd06c2..0164833 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "java.configuration.updateBuildConfiguration": "automatic", - "java.debug.settings.onBuildFailureProceed": true + "java.debug.settings.onBuildFailureProceed": true, + "java.compile.nullAnalysis.mode": "automatic" } \ No newline at end of file diff --git a/database/BSK.db-shm b/database/BSK.db-shm index 4d7a7fc0dd344ed3d9fbc31ea705dcc0d4447b5c..e729ed4f6b83d0b7d5a7b7bac5625d6f4be2c28f 100644 GIT binary patch delta 428 zcmZo@U}|V!s+V}A%K!qbK+MR%AaI|Vfx#Wf*b*z`xLPteD&WkE0Kb5U?>q-8HghWd znn|j9u;D-gWbS_?02OCqIKQ#+H(x!_Mh1Q$hS35*wj&TL0R_x}7^oY}2g!rjEIyo6L8CiVibwo{(C?&bpuZ cAoFP;zmaKjSAGiPq0I~Z4l`}uROH7C02x+s(EtDd delta 229 zcmZo@U}|V!s+V}A%K!o*K+MR%Ah4I2fx#Wf_$YqC+teZOU%;6c0e%4y-!-E@<@}$k z&qS(vu;D-gWbS_?02OCqDB9Tgn{P9VK$XE{0fCOqbq*O!lZ~7uCjWC%oBYT%Vsnl2 z4YtjXTvZq+S9mvU77(ao+Fa&mBM6jxw0TN`0W(nU4CCersU_?{RvXjguKbkEi~No- KZQfMm#|r>I15;Q4 diff --git a/database/BSK.db-wal b/database/BSK.db-wal index dcc7d2d0b84bdaf0eb80ab301474bba65a02d964..1d3f5c650438c20cc2bf874a3e6263acd300b297 100644 GIT binary patch delta 5129 zcmeHLeNMpU%z_hOHq3!)cpIDuC&64e8BT#3sD(O6LOnD< zBQ!w@njsDI_D?;dPQfq?M*}PGJy%{xVE;nhwKTMRMViw$JNo{%I7yW@1kZ1!Rzl}E64 z8{LSrJ5#6&VuTr6!ywG7*@?ZR;rf8;u_G+1OYb zoCYgFnj}kM6r~bH?4yUsY=ZrGGu=n9jYmvL0=B?Hj}vkTzF`jakY6${w$c5}_GjsH zN!HN#4?c46t;2%NN;bZqijU8^dcW$(f**R4ON=z`L*(-d$v^C{k8f3uYB5c~k6y%_cR@I9ln9XaAx^BJ)iV?=KEp3&gK-;8w3KJV)xIWo$@q{;ARHzzGH7@(}B!(qa4^V8B@2MPQlN& z+r@y{aV~H7J?KU`URYD*sBLa=$Hn9kN%)+mszxFqpUB2}xP+PUTwJuOK_pFrRi5L| zOgJM)6GBA~2KE65h*$Kq+dC9)n=ls3nu4iSqL_lF$;z_o=9Qk#br^Cr3Wl`5&kG-geaBGT`xI(QI|HrtuB|;Dm4b0G|~o5nE=I9BV_!TW!0X}ryR<=nmwKS5Ugrv zFrHMS)tM=i(V!PVF_@J{vg3aX<>pB5(k>z*=AiZ-73q9h8E_ z;5-Ph!i9gzSU-r(U5JpR`bof5*vN>&JrN*b_rczoKc3nSIHGWc5B4d}DmH+L?epr9V{o!=-;fOjL&`zX^}Q z-yg=X`jU_@1_dPhtFAw1ef!XHR#{AM6^zC?Oo!0uMB^YDEE+=gh4&{sAWz~I#gn+_dRE(-`OxJE=p0H3@-{dIFAa!r;I{Onok5OBt$(C>cmLuiH{FW zzVyd7RaI@##4r`R(=#G>Z>PS<(j?@DDwlt5kr4}4rp|)b3_sVZl zzJ#o7$!62Ep!tm9%!jW=v(t#v-!)#`?OXUuTs~-U%?{ULSbYnBiS>3E=>aw3iGc+; zY8RP=hx}eVDS6YT&X41|xn&TCdhzPGfsoXP^WzRVfq#m8(dvSyzZ&l$#wg+p60 zgRHoZQK=|Pz>d?U>JhA81oJ+A>I-S5;c%%xGrxI9Wj@K%@>h;tEj#_Y?q{!`N7nKP zwB@vXSlT97hA@4!eZiW|gr8fU+4^$X|E7NvP5;f_(F@*d6U+%OGtTr2cx5((=^I@6 zO*32U1yS^ESkoUu--aX#ujjtpXWV%qV{UKI^#AV;2m4_6jdzEfi3B^Vi`{iedmitR zU27t71AO)L>&@UtYePm98&RzOuf!CQ8IHt^IHw6pQFEEC>cf@0_C;$StdZJ>BU^u( zkzre6j9gxY*Hd0Q+2s$C_cX7JQeF{s_?q^?@(bGV6(W7yhd=6rbu_uC^l352`|i+C0F zCMGT+Ddk;SMpjNfB*mh diff --git a/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java b/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java index cb29204..3ae2cdd 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java @@ -12,6 +12,7 @@ import BsK.common.packet.res.DeleteCheckupResponse; import BsK.common.packet.res.GetCheckupDataResponse; import BsK.common.util.network.NetworkUtil; import BsK.client.ui.component.CheckUpPage.CheckUpPage; +import BsK.common.entity.CheckupData; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -28,7 +29,9 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class DataDialog extends JDialog { // @@ -57,6 +60,7 @@ public class DataDialog extends JDialog { // --- Panels for the other tabs private MedicineManagementPanel medicinePanel; private ServiceManagementPanel servicePanel; + private DoctorManagementPanel doctorPanel; private UserManagementPanel userPanel; public DataDialog(JFrame parent, CheckUpPage checkUpPage) { @@ -100,6 +104,10 @@ public class DataDialog extends JDialog { userPanel = new UserManagementPanel(); tabbedPane.addTab("Quản Lý Người Dùng", userPanel); + // --- Tab 5: Doctor Management --- + doctorPanel = new DoctorManagementPanel(); + tabbedPane.addTab("Quản Lý Bác Sĩ", doctorPanel); + add(tabbedPane, BorderLayout.CENTER); loadFiltersFromLocalStorage(); @@ -124,6 +132,9 @@ public class DataDialog extends JDialog { if (userPanel != null) { userPanel.cleanup(); } + if (doctorPanel != null) { + doctorPanel.cleanup(); + } super.windowClosing(e); } }); @@ -285,7 +296,7 @@ public class DataDialog extends JDialog { }); getAllButton.addActionListener(e -> { - GetCheckupDataRequest request = new GetCheckupDataRequest(null, null,null, null, null, 1, recordsPerPage); + GetCheckupDataRequest request = new GetCheckupDataRequest(null, null,null, null, null, 1, recordsPerPage, LocalStorage.currentShift); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); resultCountLabel.setText("Đang tải tất cả dữ liệu..."); }); @@ -380,7 +391,7 @@ public class DataDialog extends JDialog { } } - GetCheckupDataRequest request = new GetCheckupDataRequest(searchTerm, checkupIdSearch, fromTimestamp, toTimestamp, doctorId, page, recordsPerPage); + GetCheckupDataRequest request = new GetCheckupDataRequest(searchTerm, checkupIdSearch, fromTimestamp, toTimestamp, doctorId, page, recordsPerPage, LocalStorage.currentShift); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); resultCountLabel.setText("Đang tải dữ liệu cho trang " + page + "..."); @@ -679,7 +690,7 @@ public class DataDialog extends JDialog { if (confirmation == JOptionPane.YES_OPTION) { try { long checkupId = Long.parseLong(checkupIdStr); - DeleteCheckupRequest deleteRequest = new DeleteCheckupRequest(checkupId); + DeleteCheckupRequest deleteRequest = new DeleteCheckupRequest(checkupId, LocalStorage.currentShift); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), deleteRequest); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(parentDialog, "Mã khám không hợp lệ: " + checkupIdStr, "Lỗi", JOptionPane.ERROR_MESSAGE); diff --git a/src/main/java/BsK/client/ui/component/DataDialog/DoctorManagementPanel.java b/src/main/java/BsK/client/ui/component/DataDialog/DoctorManagementPanel.java new file mode 100644 index 0000000..4c6d1b2 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/DataDialog/DoctorManagementPanel.java @@ -0,0 +1,338 @@ +package BsK.client.ui.component.DataDialog; + +import BsK.client.LocalStorage; +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.common.entity.Doctor; +import BsK.common.entity.DoctorItem; +import BsK.common.packet.req.AddDoctorRequest; +import BsK.common.packet.req.EditDoctorRequest; +import BsK.common.packet.req.GetDoctorInfoRequest; +import BsK.common.packet.res.GetDoctorInfoResponse; +import BsK.common.util.network.NetworkUtil; +import BsK.common.util.text.TextUtils; +import lombok.extern.slf4j.Slf4j; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + + +@Slf4j +public class DoctorManagementPanel extends JPanel { + + private JTable doctorTable; + private DefaultTableModel doctorTableModel; + private JTextField doctorSearchField; + + private JTextField hoField; + private JTextField tenField; + private JCheckBox chkIsDeleted; + + private JButton btnAdd, btnEdit, btnClear; + + private List allDoctors = new ArrayList<>(); + private String selectedDoctorId = null; + + // --- Networking --- + private final ResponseListener getDoctorInfoResponseListener = this::handleGetDoctorInfoResponse; + + public DoctorManagementPanel() { + super(new BorderLayout(10, 10)); + initComponents(); + setupNetworking(); + } + + private void setupNetworking() { + ClientHandler.addResponseListener(GetDoctorInfoResponse.class, getDoctorInfoResponseListener); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetDoctorInfoRequest()); + } + + private void initComponents() { + this.setBorder(new EmptyBorder(15, 15, 15, 15)); + this.add(createDoctorInputPanel(), BorderLayout.WEST); + this.add(createDoctorListPanel(), BorderLayout.CENTER); + } + + private void handleGetDoctorInfoResponse(GetDoctorInfoResponse response) { + SwingUtilities.invokeLater(() -> { + if (response != null && response.getDoctors() != null) { + allDoctors = response.getDoctors(); + populateDoctorTable(); + + // Update LocalStorage.doctorsName with only non-deleted doctors + LocalStorage.doctorsName.clear(); + for (Doctor doctor : allDoctors) { + if ("0".equals(doctor.getDeleted())) { + LocalStorage.doctorsName.add(new DoctorItem( + doctor.getId(), + doctor.getFullName() + )); + } + } + log.info("Updated LocalStorage.doctorsName with {} active doctors", LocalStorage.doctorsName.size()); + } + }); + } + + private JPanel createDoctorInputPanel() { + JPanel mainInputPanel = new JPanel(new GridBagLayout()); + mainInputPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + GridBagConstraints mainGbc = new GridBagConstraints(); + mainGbc.fill = GridBagConstraints.HORIZONTAL; + mainGbc.anchor = GridBagConstraints.NORTHWEST; + mainGbc.insets = new Insets(5, 5, 5, 5); + mainGbc.weightx = 1.0; + + Font titleFont = new Font("Arial", Font.BOLD, 16); + Font labelFont = new Font("Arial", Font.BOLD, 15); + Font textFont = new Font("Arial", Font.PLAIN, 15); + Dimension textFieldSize = new Dimension(100, 30); + + JPanel doctorInfoPanel = new JPanel(new GridBagLayout()); + doctorInfoPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Thông tin bác sĩ", + TitledBorder.LEADING, TitledBorder.TOP, + titleFont, new Color(50, 50, 50) + )); + mainGbc.gridx = 0; + mainGbc.gridy = 0; + mainInputPanel.add(doctorInfoPanel, mainGbc); + + mainGbc.gridy = 1; + mainInputPanel.add(createDoctorButtonPanel(), mainGbc); + + mainGbc.gridy = 2; + mainGbc.weighty = 1.0; + mainInputPanel.add(new JPanel(), mainGbc); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(5, 5, 5, 5); + gbc.weightx = 0.0; + gbc.weighty = 0.0; + + gbc.gridy = 0; + gbc.gridx = 0; + JLabel hoLabel = new JLabel("Họ:"); + hoLabel.setFont(labelFont); + doctorInfoPanel.add(hoLabel, gbc); + + gbc.gridx = 1; + gbc.weightx = 1.0; + hoField = new JTextField(20); + hoField.setFont(textFont); + hoField.setPreferredSize(textFieldSize); + doctorInfoPanel.add(hoField, gbc); + + gbc.gridy++; + gbc.gridx = 0; + gbc.weightx = 0.0; + JLabel tenLabel = new JLabel("Tên:"); + tenLabel.setFont(labelFont); + doctorInfoPanel.add(tenLabel, gbc); + + gbc.gridx = 1; + gbc.weightx = 1.0; + tenField = new JTextField(10); + tenField.setFont(textFont); + tenField.setPreferredSize(textFieldSize); + doctorInfoPanel.add(tenField, gbc); + + return mainInputPanel; + } + + private JPanel createDoctorButtonPanel() { + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); + btnAdd = new JButton("Thêm mới"); + btnEdit = new JButton("Chỉnh sửa"); + btnClear = new JButton("Làm mới"); + chkIsDeleted = new JCheckBox("Ẩn (Xoá)"); + chkIsDeleted.setFont(new Font("Arial", Font.BOLD, 13)); + + Dimension btnSize = new Dimension(100, 35); + btnAdd.setPreferredSize(btnSize); + btnEdit.setPreferredSize(btnSize); + btnClear.setPreferredSize(btnSize); + + buttonPanel.add(chkIsDeleted); + buttonPanel.add(btnAdd); + buttonPanel.add(btnEdit); + buttonPanel.add(btnClear); + + btnClear.addActionListener(e -> clearDoctorFields()); + + // --- ADD DOCTOR ACTION --- + btnAdd.addActionListener(e -> { + // 1. Validate required fields + String ho = hoField.getText().trim(); + String ten = tenField.getText().trim(); + + if (ho.isEmpty() || ten.isEmpty()) { + JOptionPane.showMessageDialog(this, "Họ và tên không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + if (ho.isEmpty()) { + hoField.requestFocusInWindow(); + } else { + tenField.requestFocusInWindow(); + } + return; + } + + // 2. Create the request packet and send it to the server + AddDoctorRequest request = new AddDoctorRequest(ho, ten, false); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + // 3. Provide feedback and reset the form + JOptionPane.showMessageDialog(this, "Yêu cầu thêm bác sĩ '" + ho + " " + ten + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearDoctorFields(); + }); + + // --- EDIT DOCTOR ACTION --- + btnEdit.addActionListener(e -> { + // 1. Check if a doctor has been selected + if (selectedDoctorId == null) { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một bác sĩ để chỉnh sửa.", "Chưa chọn bác sĩ", JOptionPane.WARNING_MESSAGE); + return; + } + + // 2. Validate required fields + String ho = hoField.getText().trim(); + String ten = tenField.getText().trim(); + + if (ho.isEmpty() || ten.isEmpty()) { + JOptionPane.showMessageDialog(this, "Họ và tên không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + if (ho.isEmpty()) { + hoField.requestFocusInWindow(); + } else { + tenField.requestFocusInWindow(); + } + return; + } + + // 3. Gather data from all fields + String id = selectedDoctorId; + Boolean isDeleted = chkIsDeleted.isSelected(); + + // 4. Create the request packet and send it + EditDoctorRequest request = new EditDoctorRequest(id, ho, ten, isDeleted); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + // 5. Provide user feedback and clear the form + JOptionPane.showMessageDialog(this, "Yêu cầu chỉnh sửa bác sĩ '" + ho + " " + ten + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearDoctorFields(); + }); + + btnEdit.setEnabled(false); + chkIsDeleted.setEnabled(false); + + return buttonPanel; + } + + private JPanel createDoctorListPanel() { + JPanel listPanel = new JPanel(new BorderLayout(10, 10)); + listPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Danh Sách Bác Sĩ", + TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14))); + + String[] doctorColumns = {"ID", "Họ", "Tên", "Trạng thái"}; + doctorTableModel = new DefaultTableModel(doctorColumns, 0) { + @Override + public boolean isCellEditable(int row, int column) { return false; } + }; + doctorTable = new JTable(doctorTableModel); + doctorTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + doctorTable.setFont(new Font("Arial", Font.PLAIN, 12)); + doctorTable.setRowHeight(28); + + doctorTable.getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting() && doctorTable.getSelectedRow() != -1) { + int viewRow = doctorTable.getSelectedRow(); + String doctorId = doctorTableModel.getValueAt(viewRow, 0).toString(); + + allDoctors.stream() + .filter(doctor -> doctor.getId().equals(doctorId)) + .findFirst() + .ifPresent(this::populateDoctorFields); + } + }); + + JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + searchPanel.add(new JLabel("Tìm kiếm bác sĩ:")); + doctorSearchField = new JTextField(25); + searchPanel.add(doctorSearchField); + + doctorSearchField.getDocument().addDocumentListener(new DocumentListener() { + @Override public void insertUpdate(DocumentEvent e) { filterDoctorTable(); } + @Override public void removeUpdate(DocumentEvent e) { filterDoctorTable(); } + @Override public void changedUpdate(DocumentEvent e) { filterDoctorTable(); } + }); + + listPanel.add(searchPanel, BorderLayout.NORTH); + listPanel.add(new JScrollPane(doctorTable), BorderLayout.CENTER); + + return listPanel; + } + + private void populateDoctorTable() { + filterDoctorTable(); + } + + private void populateDoctorFields(Doctor doctor) { + if (doctor == null) return; + selectedDoctorId = doctor.getId(); + + hoField.setText(doctor.getLastName()); + tenField.setText(doctor.getFirstName()); + chkIsDeleted.setSelected("1".equals(doctor.getDeleted())); + + btnAdd.setEnabled(false); + btnEdit.setEnabled(true); + chkIsDeleted.setEnabled(true); + } + + private void clearDoctorFields() { + selectedDoctorId = null; + hoField.setText(""); + tenField.setText(""); + chkIsDeleted.setSelected(false); + doctorTable.clearSelection(); + hoField.requestFocusInWindow(); + + btnAdd.setEnabled(true); + btnEdit.setEnabled(false); + chkIsDeleted.setEnabled(false); + } + + private void filterDoctorTable() { + String filterText = doctorSearchField.getText().trim(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); + + doctorTableModel.setRowCount(0); + + for (Doctor doctor : allDoctors) { + String fullName = doctor.getFullName(); + if (filterText.isEmpty() || TextUtils.removeAccents(fullName.toLowerCase()).contains(lowerCaseFilterText)) { + String status = "0".equals(doctor.getDeleted()) ? "Hoạt động" : "Đã ẩn"; + + doctorTableModel.addRow(new Object[]{ + doctor.getId(), + doctor.getLastName(), + doctor.getFirstName(), + status + }); + } + } + } + + public void cleanup() { + ClientHandler.deleteListener(GetDoctorInfoResponse.class, getDoctorInfoResponseListener); + } +} + diff --git a/src/main/java/BsK/client/ui/component/common/NavBar.java b/src/main/java/BsK/client/ui/component/common/NavBar.java index a8fd8fa..f1b2678 100644 --- a/src/main/java/BsK/client/ui/component/common/NavBar.java +++ b/src/main/java/BsK/client/ui/component/common/NavBar.java @@ -32,6 +32,11 @@ import java.awt.event.ActionListener; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Calendar; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Slf4j public class NavBar extends JPanel { @@ -281,6 +286,15 @@ public class NavBar extends JPanel { // --- END MODIFIED SEPARATOR --- } + String version = getClientVersion(); + if (!"N/A".equals(version)) { + JLabel versionLabel = new JLabel("Phiên bản: " + version); + versionLabel.setFont(new Font("Arial", Font.PLAIN, 10)); + versionLabel.setForeground(new Color(120, 120, 120)); + versionLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); + navItemsPanel.add(versionLabel); + } + this.add(navItemsPanel); this.add(Box.createHorizontalGlue()); this.add(titlePanel); @@ -752,4 +766,18 @@ public class NavBar extends JPanel { ClientHandler.deleteListener(SetCounterResponse.class, setCounterResponseListener); // NEW: Clean up counter listener log.info("All NavBar listeners removed."); } + + private String getClientVersion() { + try { + String content = new String(Files.readAllBytes(Paths.get("version_client.json"))); + Pattern pattern = Pattern.compile("\"currentVersion\":\\s*\"([^\"]*)\""); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + return matcher.group(1); + } + } catch (IOException e) { + log.error("Error reading client version file", e); + } + return "N/A"; + } } \ No newline at end of file diff --git a/src/main/java/BsK/common/entity/Doctor.java b/src/main/java/BsK/common/entity/Doctor.java new file mode 100644 index 0000000..ba7d85d --- /dev/null +++ b/src/main/java/BsK/common/entity/Doctor.java @@ -0,0 +1,49 @@ +package BsK.common.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Entity class for Doctor + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Doctor { + private String id; + private String lastName; + private String firstName; + private String deleted; // 0: active, 1: deleted + + /** + * Constructor to create Doctor from array data + * @param data Array of doctor data from backend [id, lastName, firstName, deleted] + */ + public Doctor(String[] data) { + if (data.length < 4) { + throw new IllegalArgumentException("Doctor data array must contain at least 4 elements"); + } + this.id = data[0]; + this.lastName = data[1]; + this.firstName = data[2]; + this.deleted = data[3]; + } + + /** + * Get the full name of the doctor + * @return Full name (lastName + firstName) + */ + public String getFullName() { + return (lastName + " " + firstName).trim(); + } + + /** + * Convert the Doctor entity to a String array + * @return String array representation of Doctor + */ + public String[] toStringArray() { + return new String[]{id, lastName, firstName, deleted}; + } +} + diff --git a/src/main/java/BsK/common/entity/DoctorItem.java b/src/main/java/BsK/common/entity/DoctorItem.java index 2d05ec3..9dcdb09 100644 --- a/src/main/java/BsK/common/entity/DoctorItem.java +++ b/src/main/java/BsK/common/entity/DoctorItem.java @@ -8,7 +8,7 @@ import lombok.Getter; public class DoctorItem { private String id; private String name; - + /** * This is the crucial part. JComboBox will use this method to display the * item in the dropdown. diff --git a/src/main/java/BsK/common/packet/req/AddDoctorRequest.java b/src/main/java/BsK/common/packet/req/AddDoctorRequest.java new file mode 100644 index 0000000..d96d160 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/AddDoctorRequest.java @@ -0,0 +1,13 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class AddDoctorRequest implements Packet { + private String lastName; + private String firstName; + private Boolean deleted; +} diff --git a/src/main/java/BsK/common/packet/req/EditDoctorRequest.java b/src/main/java/BsK/common/packet/req/EditDoctorRequest.java new file mode 100644 index 0000000..bcf2cbc --- /dev/null +++ b/src/main/java/BsK/common/packet/req/EditDoctorRequest.java @@ -0,0 +1,14 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EditDoctorRequest implements Packet { + private String id; + private String lastName; + private String firstName; + private Boolean deleted; +} diff --git a/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java b/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java index a78e6bc..f0aa474 100644 --- a/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java +++ b/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java @@ -16,4 +16,5 @@ public class GetCheckupDataRequest implements Packet { private Integer doctorId; private int page = 1; private int pageSize = 20; + private int shift; } \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/GetDoctorInfoRequest.java b/src/main/java/BsK/common/packet/req/GetDoctorInfoRequest.java new file mode 100644 index 0000000..3b71cfb --- /dev/null +++ b/src/main/java/BsK/common/packet/req/GetDoctorInfoRequest.java @@ -0,0 +1,11 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class GetDoctorInfoRequest implements Packet { +} + diff --git a/src/main/java/BsK/common/packet/req/LoginRequest.java b/src/main/java/BsK/common/packet/req/LoginRequest.java index 58c35a2..4f20fd3 100644 --- a/src/main/java/BsK/common/packet/req/LoginRequest.java +++ b/src/main/java/BsK/common/packet/req/LoginRequest.java @@ -12,4 +12,5 @@ import java.time.LocalDateTime; public class LoginRequest implements Packet { String username; String password; + int shift; } \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/res/GetDoctorInfoResponse.java b/src/main/java/BsK/common/packet/res/GetDoctorInfoResponse.java new file mode 100644 index 0000000..45df09f --- /dev/null +++ b/src/main/java/BsK/common/packet/res/GetDoctorInfoResponse.java @@ -0,0 +1,50 @@ +package BsK.common.packet.res; + +import BsK.common.entity.Doctor; +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +public class GetDoctorInfoResponse implements Packet { + private String[][] doctorInfo; + private transient List doctors; + + public GetDoctorInfoResponse(String[][] doctorInfo) { + this.doctorInfo = doctorInfo; + this.doctors = convertToDoctorList(doctorInfo); + } + + /** + * Converts the raw string array data to a list of Doctor objects + * @param doctorInfo Raw doctor data as string arrays + * @return List of Doctor objects + */ + private List convertToDoctorList(String[][] doctorInfo) { + List result = new ArrayList<>(); + if (doctorInfo != null) { + for (String[] data : doctorInfo) { + if (data.length >= 4) { + result.add(new Doctor(data)); + } + } + } + return result; + } + + /** + * Returns the doctor list, converting from raw data if needed + * @return List of Doctor objects + */ + public List getDoctors() { + if (doctors == null && doctorInfo != null) { + doctors = convertToDoctorList(doctorInfo); + } + return doctors; + } +} + diff --git a/src/main/java/BsK/server/ServerDashboard.java b/src/main/java/BsK/server/ServerDashboard.java index 67f2b73..d127837 100644 --- a/src/main/java/BsK/server/ServerDashboard.java +++ b/src/main/java/BsK/server/ServerDashboard.java @@ -7,12 +7,17 @@ import javax.swing.text.*; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.OperatingSystemMXBean; +import java.nio.file.Files; +import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import BsK.server.network.entity.ClientConnection; import BsK.server.network.manager.SessionManager; import lombok.extern.slf4j.Slf4j; @@ -55,7 +60,12 @@ public class ServerDashboard extends JFrame { } private ServerDashboard() { - setTitle("Bảng Điều Khiển Máy Chủ BSK"); + String version = getServerVersion(); + String title = "Bảng Điều Khiển Máy Chủ BSK"; + if (!"N/A".equals(version)) { + title += " - Phiên bản " + version; + } + setTitle(title); setSize(1400, 900); setLocationRelativeTo(null); @@ -650,5 +660,19 @@ public class ServerDashboard extends JFrame { } // If the user chooses "No", the dialog closes and nothing else happens. } + + private String getServerVersion() { + try { + String content = new String(Files.readAllBytes(Paths.get("version_server.json"))); + Pattern pattern = Pattern.compile("\"currentVersion\":\\s*\"([^\"]*)\""); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + return matcher.group(1); + } + } catch (IOException e) { + log.error("Error reading server version file", e); + } + return "N/A"; + } // --- MODIFICATION END --- } \ 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 f5ce101..ca6f4ea 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -1868,7 +1868,7 @@ public class ServerHandler extends SimpleChannelInboundHandler 0) { + log.info("Updated doctor successfully: {} {}", editDoctorRequest.getLastName(), editDoctorRequest.getFirstName()); + getDoctorInfo(currentUser.getSessionId()); + } else { + log.warn("No doctor found with ID {} to update.", editDoctorRequest.getId()); + } + + } catch (SQLException e) { + log.error("Error updating doctor", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); + } + } + if (packet instanceof GetAllUserInfoRequest) { if (!currentUser.getRole().equals(Role.ADMIN)) { UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.ACCESS_DENIED)); @@ -2159,6 +2219,55 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); + while (rs.next()) { + String doctorId = rs.getString("doctor_id"); + String doctorLastName = rs.getString("doctor_last_name"); + String doctorFirstName = rs.getString("doctor_first_name"); + + // Lấy giá trị boolean một cách an toàn và chuyển thành String "1" hoặc "0" + String deleted = rs.getBoolean("deleted") ? "1" : "0"; + + String result = String.join("|", + doctorId, + doctorLastName != null ? doctorLastName : "", + doctorFirstName != null ? doctorFirstName : "", + deleted + ); + 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("\\|", -1); + } + + UserUtil.sendPacket(sessionId, new GetDoctorInfoResponse(resultArray)); + + } catch (SQLException e) { + log.error("Error fetching doctors", e); + UserUtil.sendPacket(sessionId, new ErrorResponse(Error.SQL_EXCEPTION)); + } + } + /** * Lấy danh sách bệnh nhân cần tái khám trong vòng 7 ngày tới và gửi cho một session cụ thể. * diff --git a/src/main/resources/print_forms/ultrasoundresult.jrxml b/src/main/resources/print_forms/ultrasoundresult.jrxml index ca92b09..f289b14 100644 --- a/src/main/resources/print_forms/ultrasoundresult.jrxml +++ b/src/main/resources/print_forms/ultrasoundresult.jrxml @@ -66,13 +66,13 @@ - - + + - + @@ -102,9 +102,6 @@ - - - @@ -114,9 +111,6 @@ - - - @@ -258,7 +252,7 @@ - + diff --git a/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml b/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml index 2b075a9..30d18cf 100644 --- a/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml +++ b/src/main/resources/print_forms/ultrasoundresult_potrait.jrxml @@ -71,13 +71,13 @@ - + - + @@ -89,7 +89,7 @@ - + @@ -107,9 +107,6 @@ - - - @@ -119,9 +116,6 @@ - - - @@ -191,7 +185,7 @@ - +