From bbf8c9fd102bcf40d526c76b2f51f04b42452093 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: Wed, 30 Jul 2025 18:49:28 +0700 Subject: [PATCH] [update] Enhance LocalStorage and DataDialog for improved patient data management. Add new fields for search term, date range, and doctor selection in LocalStorage. Implement data loading from LocalStorage in DataDialog to maintain user filter preferences. Refactor CheckUpPage to load patient data by checkup ID, improving data retrieval accuracy. Remove unused NavBar component to streamline UI. --- src/main/java/BsK/client/LocalStorage.java | 7 + .../java/BsK/client/ui/assets/icon/view.png | Bin 0 -> 18935 bytes .../ui/component/CheckUpPage/CheckUpPage.java | 31 +- .../DashboardPage/DashboardPage.java | 61 +-- .../ui/component/DataDialog/DataDialog.java | 504 +++++++++++------- .../ui/component/LoginPage/LoginPage.java | 1 + .../java/BsK/client/ui/component/NavBar.java | 1 - .../client/ui/component/common/NavBar.java | 209 +++++++- src/main/java/BsK/common/Error.java | 11 + .../common/packet/req/EmergencyRequest.java | 11 + .../packet/req/GetCheckupDataRequest.java | 18 + .../common/packet/res/EmergencyResponse.java | 12 + .../BsK/common/packet/res/ErrorResponse.java | 33 +- .../packet/res/GetCheckupDataResponse.java | 17 + .../server/network/handler/ServerHandler.java | 162 ++++++ 15 files changed, 823 insertions(+), 255 deletions(-) create mode 100644 src/main/java/BsK/client/ui/assets/icon/view.png delete mode 100644 src/main/java/BsK/client/ui/component/NavBar.java create mode 100644 src/main/java/BsK/common/Error.java create mode 100644 src/main/java/BsK/common/packet/req/EmergencyRequest.java create mode 100644 src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java create mode 100644 src/main/java/BsK/common/packet/res/EmergencyResponse.java create mode 100644 src/main/java/BsK/common/packet/res/GetCheckupDataResponse.java diff --git a/src/main/java/BsK/client/LocalStorage.java b/src/main/java/BsK/client/LocalStorage.java index 96e0b46..d5536fc 100644 --- a/src/main/java/BsK/client/LocalStorage.java +++ b/src/main/java/BsK/client/LocalStorage.java @@ -10,6 +10,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Date; + @AllArgsConstructor @Data public class LocalStorage { @@ -21,6 +23,11 @@ public class LocalStorage { public static String ClinicAddress = "Số ABC, Đường XYZ, Quận 1, TP.HCM"; public static String ClinicPhone = "0123456789"; + public static String dataDialogSearchTerm = ""; + public static Date dataDialogFromDate = new Date(); // Default to today + public static Date dataDialogToDate = new Date(); // Default to today + public static String dataDialogDoctorName = "Tất cả"; // Default to "All" + public static List doctorsName = new ArrayList<>(); public static String[] provinces = new String[]{}; public static String[] wards = new String[]{}; diff --git a/src/main/java/BsK/client/ui/assets/icon/view.png b/src/main/java/BsK/client/ui/assets/icon/view.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b58c00a081bdb141607dfd078b65392dbfb04b GIT binary patch literal 18935 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 literal 0 HcmV?d00001 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 f0d6c0c..ee00705 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -2518,9 +2518,6 @@ public class CheckUpPage extends JPanel { }); } - private void handleErrorResponse(ErrorResponse response) { - log.error("Error response: {}", response.getError()); - } private void handleFreeRoom() { int selectedRoomIndex = callRoomComboBox.getSelectedIndex(); @@ -4941,6 +4938,34 @@ public class CheckUpPage extends JPanel { return null; } + public void loadPatientByCheckupId(String checkupId) { + if (checkupId == null || patientQueue == null) { + return; + } + + // Find the index of the patient with the matching checkupId in the current queue + int patientIndex = -1; + for (int i = 0; i < patientQueue.size(); i++) { + if (checkupId.equals(patientQueue.get(i).getCheckupId())) { + patientIndex = i; + break; + } + } + + if (patientIndex != -1) { + // Found the patient, now call the existing handler to load their data + final int finalIndex = patientIndex; + SwingUtilities.invokeLater(() -> { + handleRowSelection(finalIndex); + }); + } else { + // Optional: Handle case where patient from database isn't in the live queue + JOptionPane.showMessageDialog(this, + "Bệnh nhân với mã khám " + checkupId + " không có trong danh sách chờ hiện tại.", + "Không Tìm Thấy", + JOptionPane.WARNING_MESSAGE); + } + } } diff --git a/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java b/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java index ae70f37..a475b5f 100644 --- a/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java +++ b/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java @@ -169,27 +169,18 @@ public class DashboardPage extends JPanel { lastUpdateLabel = new JLabel("Cập nhật lần cuối: " + getCurrentTime()); lastUpdateLabel.setFont(new Font("Segoe UI", Font.BOLD, 14)); lastUpdateLabel.setForeground(new Color(107, 114, 128)); - JButton refreshButton = new JButton("Làm mới"); - refreshButton.setFont(new Font("Segoe UI", Font.BOLD, 12)); - refreshButton.setBackground(new Color(59, 130, 246)); - refreshButton.setForeground(Color.WHITE); - refreshButton.setFocusPainted(false); - refreshButton.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16)); - refreshButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - refreshButton.addActionListener(e -> refreshDashboardData()); JPanel leftPanel = new JPanel(new BorderLayout()); leftPanel.setOpaque(false); leftPanel.add(welcomeLabel, BorderLayout.NORTH); leftPanel.add(lastUpdateLabel, BorderLayout.SOUTH); headerPanel.add(leftPanel, BorderLayout.WEST); - headerPanel.add(refreshButton, BorderLayout.EAST); return headerPanel; } private JPanel createMetricsPanel() { JPanel metricsPanel = new JPanel(new GridLayout(1, 4, 20, 0)); metricsPanel.setOpaque(false); - String[] cardTitles = {"Bệnh nhân trong ngày", "Đang đợi khám", "Cần tái khám", "Ngày giờ hiện tại"}; + String[] cardTitles = {"Bệnh nhân trong ngày", "Đang đợi khám", "Cần tái khám hôm nay", "Ngày giờ hiện tại"}; Color[] cardColors = { new Color(59, 130, 246), new Color(245, 158, 11), new Color(139, 92, 246), new Color(16, 185, 129) @@ -203,7 +194,6 @@ public class DashboardPage extends JPanel { } else { RoundedPanel card = createMetricCard(cardTitles[i], cardColors[i], cardIcons[i]); - // --- MODIFIED SECTION START --- // This logic correctly finds the value label, which is a direct child of the card. for (Component comp : card.getComponents()) { if (comp instanceof JLabel && ((JLabel) comp).getFont().getSize() == 32) { @@ -211,7 +201,6 @@ public class DashboardPage extends JPanel { break; // Found the label, exit the inner loop } } - // --- MODIFIED SECTION END --- metricsPanel.add(card); } @@ -292,7 +281,11 @@ public class DashboardPage extends JPanel { titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18)); titleLabel.setForeground(new Color(31, 41, 55)); titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0)); - String[] columns = {"STT", "Mã Khám", "Họ và Tên", "Năm sinh", "Loại khám", "Trạng thái"}; + + // --- MODIFIED SECTION START --- + String[] columns = {"STT", "Mã Khám", "Họ và Tên", "Năm sinh", "Bác sĩ", "Loại khám", "Trạng thái"}; + // --- MODIFIED SECTION END --- + DefaultTableModel tableModel = new DefaultTableModel(columns, 0) { public boolean isCellEditable(int row, int column) { return false; } }; @@ -310,14 +303,17 @@ public class DashboardPage extends JPanel { return panel; } + // --- MODIFIED SECTION START --- private void setQueueTableColumnWidths() { - currentQueueTable.getColumnModel().getColumn(0).setPreferredWidth(40); - currentQueueTable.getColumnModel().getColumn(1).setPreferredWidth(80); - currentQueueTable.getColumnModel().getColumn(2).setPreferredWidth(200); - currentQueueTable.getColumnModel().getColumn(3).setPreferredWidth(90); - currentQueueTable.getColumnModel().getColumn(4).setPreferredWidth(100); - currentQueueTable.getColumnModel().getColumn(5).setPreferredWidth(100); + currentQueueTable.getColumnModel().getColumn(0).setPreferredWidth(40); // STT: small + currentQueueTable.getColumnModel().getColumn(1).setPreferredWidth(80); // Mã Khám: small + currentQueueTable.getColumnModel().getColumn(2).setPreferredWidth(180); // Họ và Tên: biggest + currentQueueTable.getColumnModel().getColumn(3).setPreferredWidth(70); // Năm sinh: small + currentQueueTable.getColumnModel().getColumn(4).setPreferredWidth(180); // Bác sĩ: large + currentQueueTable.getColumnModel().getColumn(5).setPreferredWidth(100); // Loại khám: small + currentQueueTable.getColumnModel().getColumn(6).setPreferredWidth(120); // Trạng thái: small but bigger } + // --- MODIFIED SECTION END --- private JPanel createActionButtonsPanel() { RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false); @@ -329,11 +325,11 @@ public class DashboardPage extends JPanel { JPanel actionsGrid = new JPanel(new GridLayout(2, 2, 15, 15)); actionsGrid.setOpaque(false); actionsGrid.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - String[] actionTitles = {"Thêm bệnh nhân", "Gọi bệnh nhân tái khám", "Báo cáo ngày", "Khẩn cấp"}; - String[] actionIcons = {"➕", "📞", "📊", "🚨"}; + String[] actionTitles = {"Thêm bệnh nhân", "Gọi bệnh nhân tái khám", "Báo cáo ngày", "Quản lý dịch vụ (thuốc)"}; + String[] actionIcons = {"➕", "📞", "📊", "📋"}; Color[] actionColors = { new Color(34, 197, 94), new Color(59, 130, 246), - new Color(168, 85, 247), new Color(239, 68, 68) + new Color(168, 85, 247), new Color(24, 68, 68) }; for (int i = 0; i < actionTitles.length; i++) { JButton actionButton = createActionButton(actionTitles[i], actionIcons[i], actionColors[i]); @@ -396,19 +392,22 @@ public class DashboardPage extends JPanel { if (patientQueue != null) { for (Patient patient : patientQueue) { + // --- MODIFIED SECTION START --- Object[] rowData = { patient.getQueueNumber(), patient.getCheckupId(), patient.getCustomerLastName() + " " + patient.getCustomerFirstName(), DateUtils.extractYearFromTimestamp(patient.getCustomerDob()), + patient.getDoctorName(), // Added doctor name patient.getCheckupType(), patient.getStatus() }; + // --- MODIFIED SECTION END --- model.addRow(rowData); } } - long waitingCount = patientQueue.stream().filter(p -> "CHỜ KHÁM".equals(p.getStatus())).count(); + long waitingCount = patientQueue.stream().filter(p -> "CHỜ KHÁM".equals(p.getStatus())).count() ; if (waitingPatientsLabel != null) { waitingPatientsLabel.setText(String.valueOf(waitingCount)); } @@ -416,14 +415,6 @@ public class DashboardPage extends JPanel { setQueueTableColumnWidths(); } - private void refreshDashboardData() { - lastUpdateLabel.setText("Cập nhật lần cuối: " + getCurrentTime()); - NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetCheckUpQueueUpdateRequest()); - JOptionPane.showMessageDialog(this, - "Đã gửi yêu cầu làm mới dữ liệu!", - "Thông báo", - JOptionPane.INFORMATION_MESSAGE); - } private void handleAction(int actionIndex) { switch (actionIndex) { @@ -437,12 +428,12 @@ public class DashboardPage extends JPanel { break; case 2: // Daily report JOptionPane.showMessageDialog(this, - "Chức năng báo cáo ngày đang được phát triển", - "Thông báo", - JOptionPane.INFORMATION_MESSAGE); + "Chức năng báo cáo ngày đang được phát triển", + "Thông báo", + JOptionPane.INFORMATION_MESSAGE); break; case 3: - JOptionPane.showMessageDialog(this, "Chế độ khẩn cấp được kích hoạt!", "KHẨN CẤP", JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(this, "Chế độ khẩn cấp được kích hoạt!", "DỊCH VỤ", JOptionPane.WARNING_MESSAGE); break; } } 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 a2b8979..ec393b9 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/DataDialog.java @@ -1,6 +1,15 @@ package BsK.client.ui.component.DataDialog; +import BsK.client.LocalStorage; +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; import BsK.client.ui.component.common.AddDialog.AddDialog; +import BsK.common.entity.DoctorItem; +import BsK.common.entity.Patient; +import BsK.common.packet.req.GetCheckupDataRequest; +import BsK.common.packet.res.GetCheckupDataResponse; +import BsK.common.util.network.NetworkUtil; +import BsK.client.ui.component.CheckUpPage.CheckUpPage; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -8,13 +17,13 @@ import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.text.SimpleDateFormat; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Calendar; import java.util.Date; public class DataDialog extends JDialog { - + private JTextField searchField; private JComboBox doctorComboBox; private JSpinner fromDateSpinner; @@ -22,50 +31,98 @@ public class DataDialog extends JDialog { private JTable dataTable; private DefaultTableModel tableModel; private JLabel resultCountLabel; - private JButton prevPageButton, nextPageButton, firstPageButton, lastPageButton; - private JLabel currentPageLabel; - + + private final ResponseListener dataResponseListener = this::handleGetCheckupDataResponse; + private int currentPage = 1; - private int totalPages = 8; + private int totalPages = 1; private int recordsPerPage = 20; - private int totalRecords = 157; - + private int totalRecords = 0; + private JPanel mainPanel; private JPanel paginationPanel; - - public DataDialog(JFrame parent) { + private CheckUpPage checkUpPageInstance; // <-- ADD THIS FIELD + + // --- MODIFIED CONSTRUCTOR --- + public DataDialog(JFrame parent, CheckUpPage checkUpPage) { super(parent, "Quản Lý Dữ Liệu Khám Bệnh", true); + this.checkUpPageInstance = checkUpPage; // Store the instance + initializeDialog(); - loadFakeData(); + setupNetworking(); + fetchData(1); } - + private void initializeDialog() { setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setSize(1200, 800); setLocationRelativeTo(getParent()); setLayout(new BorderLayout()); - - // Create main panel with padding + mainPanel = new JPanel(new BorderLayout()); mainPanel.setBorder(new EmptyBorder(20, 20, 20, 20)); - - // Add components + mainPanel.add(createControlPanel(), BorderLayout.NORTH); mainPanel.add(createDataGridPanel(), BorderLayout.CENTER); - paginationPanel = createPaginationPanel(); - mainPanel.add(paginationPanel, BorderLayout.SOUTH); + + paginationPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); + mainPanel.add(paginationPanel, BorderLayout.SOUTH); add(mainPanel); + loadFiltersFromLocalStorage(); // <-- CALL THE NEW METHOD HERE } + // --- ADD THIS NEW METHOD --- + private void loadFiltersFromLocalStorage() { + // Load search term, handling placeholder text + if (LocalStorage.dataDialogSearchTerm == null || LocalStorage.dataDialogSearchTerm.isEmpty()) { + searchField.setText("Tìm theo tên bệnh nhân, mã bệnh nhân..."); + searchField.setForeground(Color.GRAY); + } else { + searchField.setText(LocalStorage.dataDialogSearchTerm); + searchField.setForeground(Color.BLACK); + } + + // Load dates, providing a default if null + if (LocalStorage.dataDialogFromDate != null) { + fromDateSpinner.setValue(LocalStorage.dataDialogFromDate); + } else { + fromDateSpinner.setValue(new Date()); + } + + if (LocalStorage.dataDialogToDate != null) { + toDateSpinner.setValue(LocalStorage.dataDialogToDate); + } else { + toDateSpinner.setValue(new Date()); + } + + // Load doctor selection + if (LocalStorage.dataDialogDoctorName != null) { + doctorComboBox.setSelectedItem(LocalStorage.dataDialogDoctorName); + } else { + doctorComboBox.setSelectedIndex(0); // Default to "Tất cả" + } + } + + private void setupNetworking() { + ClientHandler.addResponseListener(GetCheckupDataResponse.class, dataResponseListener); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + // --- MODIFIED: Changed to deleteListener as requested --- + ClientHandler.deleteListener(GetCheckupDataResponse.class, dataResponseListener); + super.windowClosing(e); + } + }); + } + + + private JPanel createControlPanel() { JPanel controlPanel = new JPanel(new BorderLayout()); controlPanel.setBorder(BorderFactory.createTitledBorder("Tìm kiếm và Lọc dữ liệu")); - - // Top row with search and main action buttons + JPanel topRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); - - // Search field searchField = new JTextField("Tìm theo tên bệnh nhân, mã bệnh nhân...", 25); searchField.setForeground(Color.GRAY); searchField.addFocusListener(new java.awt.event.FocusAdapter() { @@ -82,25 +139,25 @@ public class DataDialog extends JDialog { } } }); - + JButton filterButton = new JButton("Lọc"); filterButton.setBackground(new Color(51, 135, 204)); filterButton.setForeground(Color.WHITE); filterButton.setPreferredSize(new Dimension(80, 30)); - + JButton clearFilterButton = new JButton("Xóa bộ lọc"); clearFilterButton.setPreferredSize(new Dimension(100, 30)); - + JButton exportExcelButton = new JButton("Xuất Excel"); exportExcelButton.setBackground(new Color(66, 157, 21)); exportExcelButton.setForeground(Color.WHITE); exportExcelButton.setPreferredSize(new Dimension(100, 30)); - + JButton addNewButton = new JButton("+ Thêm mới"); addNewButton.setBackground(new Color(200, 138, 16)); addNewButton.setForeground(Color.WHITE); addNewButton.setPreferredSize(new Dimension(120, 30)); - + topRow.add(new JLabel("🔍")); topRow.add(searchField); topRow.add(filterButton); @@ -108,66 +165,71 @@ public class DataDialog extends JDialog { topRow.add(Box.createHorizontalStrut(20)); topRow.add(exportExcelButton); topRow.add(addNewButton); - - // Bottom row with advanced filters + JPanel bottomRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); - bottomRow.add(new JLabel("Từ ngày:")); fromDateSpinner = new JSpinner(new SpinnerDateModel()); fromDateSpinner.setEditor(new JSpinner.DateEditor(fromDateSpinner, "dd/MM/yyyy")); fromDateSpinner.setPreferredSize(new Dimension(100, 25)); bottomRow.add(fromDateSpinner); - + bottomRow.add(new JLabel("Đến ngày:")); toDateSpinner = new JSpinner(new SpinnerDateModel()); toDateSpinner.setEditor(new JSpinner.DateEditor(toDateSpinner, "dd/MM/yyyy")); toDateSpinner.setPreferredSize(new Dimension(100, 25)); bottomRow.add(toDateSpinner); - + bottomRow.add(Box.createHorizontalStrut(20)); bottomRow.add(new JLabel("Bác sĩ:")); - String[] doctors = {"Tất cả", "BS. Nguyễn Thiên Phúc", "BS. Trần Minh Anh", "BS. Lê Hoàng Nam", "BS. Phạm Thị Lan"}; - doctorComboBox = new JComboBox<>(doctors); + + // --- MODIFIED: Use DoctorItem from LocalStorage --- + doctorComboBox = new JComboBox<>(); + // Add "Tất cả" option first + doctorComboBox.addItem("Tất cả"); + // Add doctors from LocalStorage + for (DoctorItem doctor : LocalStorage.doctorsName) { + doctorComboBox.addItem(doctor.getName()); + } + doctorComboBox.setSelectedIndex(0); // Default to "Tất cả" doctorComboBox.setPreferredSize(new Dimension(150, 25)); bottomRow.add(doctorComboBox); - + controlPanel.add(topRow, BorderLayout.NORTH); controlPanel.add(bottomRow, BorderLayout.SOUTH); - - // Add action listeners + addNewButton.addActionListener(e -> { AddDialog addDialog = new AddDialog((Frame) getParent()); addDialog.setVisible(true); }); + exportExcelButton.addActionListener(e -> JOptionPane.showMessageDialog(this, "Chức năng xuất Excel đang được phát triển", "Thông báo", JOptionPane.INFORMATION_MESSAGE)); - exportExcelButton.addActionListener(e -> { - JOptionPane.showMessageDialog(this, "Chức năng xuất Excel đang được phát triển", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + filterButton.addActionListener(e -> { + saveFiltersToLocalStorage(); // <-- SAVE STATE BEFORE FETCHING + fetchData(1); }); - - filterButton.addActionListener(e -> applyFilters()); - clearFilterButton.addActionListener(e -> clearFilters()); - + clearFilterButton.addActionListener(e -> { + clearFilters(); + fetchData(1); + }); + return controlPanel; } - + private JPanel createDataGridPanel() { JPanel gridPanel = new JPanel(new BorderLayout()); - - // Result count label - resultCountLabel = new JLabel("Hiển thị 1 đến 20 của 157 kết quả"); + resultCountLabel = new JLabel("Đang tải dữ liệu..."); resultCountLabel.setBorder(new EmptyBorder(10, 0, 10, 0)); resultCountLabel.setFont(new Font("Arial", Font.PLAIN, 12)); gridPanel.add(resultCountLabel, BorderLayout.NORTH); - - // Create table - String[] columnNames = {"STT", "Mã BN", "Họ và Tên", "Ngày khám", "Bác sĩ khám", "Kết luận", "Hành động"}; + + // --- MODIFIED: Added more columns for better data visibility --- + String[] columnNames = {"STT", "Mã BN", "Họ và Tên", "Năm sinh", "Giới tính", "Ngày khám", "Bác sĩ khám", "Chẩn đoán", "Kết luận", "Hành động"}; tableModel = new DefaultTableModel(columnNames, 0) { @Override public boolean isCellEditable(int row, int column) { - return column == 6; // Only actions column is editable + return column == 9; // Action column is now at index 9 } }; - dataTable = new JTable(tableModel); dataTable.setRowHeight(35); dataTable.setFont(new Font("Arial", Font.PLAIN, 12)); @@ -175,156 +237,229 @@ public class DataDialog extends JDialog { dataTable.getTableHeader().setBackground(new Color(240, 240, 240)); dataTable.setSelectionBackground(new Color(230, 240, 255)); - // Set column widths + // --- MODIFIED: Adjusted column widths for the new layout --- TableColumn column; - int[] columnWidths = {50, 80, 150, 100, 130, 200, 120}; + int[] columnWidths = {40, 80, 150, 80, 60, 100, 130, 150, 200, 120}; for (int i = 0; i < columnWidths.length; i++) { column = dataTable.getColumnModel().getColumn(i); column.setPreferredWidth(columnWidths[i]); } - - // Custom renderer for actions column + dataTable.getColumn("Hành động").setCellRenderer(new ActionButtonRenderer()); - dataTable.getColumn("Hành động").setCellEditor(new ActionButtonEditor()); + dataTable.getColumn("Hành động").setCellEditor(new ActionButtonEditor(this)); JScrollPane scrollPane = new JScrollPane(dataTable); - scrollPane.setBorder(BorderFactory.createLoweredBevelBorder()); gridPanel.add(scrollPane, BorderLayout.CENTER); - return gridPanel; } - - private JPanel createPaginationPanel() { - JPanel paginationPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); + + private void fetchData(int page) { + String searchTerm = searchField.getText(); + if (searchTerm.equals("Tìm theo tên bệnh nhân, mã bệnh nhân...")) { + searchTerm = null; + } + + Date fromDate = (Date) fromDateSpinner.getValue(); + Date toDate = (Date) toDateSpinner.getValue(); + + Calendar cal = Calendar.getInstance(); + cal.setTime(fromDate); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + long fromTimestamp = cal.getTimeInMillis(); + + cal.setTime(toDate); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 59); + cal.set(Calendar.SECOND, 59); + long toTimestamp = cal.getTimeInMillis(); + + String selectedDoctor = (String) doctorComboBox.getSelectedItem(); + Integer doctorId = null; - firstPageButton = new JButton("<<"); - prevPageButton = new JButton("<"); - nextPageButton = new JButton(">"); - lastPageButton = new JButton(">>"); + // --- MODIFIED: Get correct doctor ID from LocalStorage --- + if (selectedDoctor != null && !selectedDoctor.equals("Tất cả")) { + for (DoctorItem doctor : LocalStorage.doctorsName) { + if (doctor.getName().equals(selectedDoctor)) { + try { + doctorId = Integer.parseInt(doctor.getId()); + } catch (NumberFormatException e) { + System.err.println("Invalid doctor ID format: " + doctor.getId()); + } + break; + } + } + } + + GetCheckupDataRequest request = new GetCheckupDataRequest(searchTerm, fromTimestamp, toTimestamp, doctorId, page, recordsPerPage); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); - currentPageLabel = new JLabel("1"); + resultCountLabel.setText("Đang tải dữ liệu cho trang " + page + "..."); + } + + // --- MODIFIED: Process raw data into Patient DTOs and populate table correctly --- + private void handleGetCheckupDataResponse(GetCheckupDataResponse response) { + SwingUtilities.invokeLater(() -> { + this.currentPage = response.getCurrentPage(); + this.totalPages = response.getTotalPages(); + this.totalRecords = response.getTotalRecords(); + this.recordsPerPage = response.getPageSize(); + + tableModel.setRowCount(0); + if (response.getCheckupData() != null) { + int stt = (currentPage - 1) * recordsPerPage + 1; + for (String[] rowData : response.getCheckupData()) { + try { + Patient patient = new Patient(rowData); + String[] tableRow = { + String.valueOf(stt++), + patient.getCustomerId(), // Correctly use customerId for "Mã BN" + patient.getCustomerLastName() + " " + patient.getCustomerFirstName(), + patient.getCustomerDob(), + patient.getCustomerGender(), + patient.getCheckupDate(), + patient.getDoctorName(), + patient.getDiagnosis(), + patient.getConclusion(), + "" // Action column placeholder + }; + tableModel.addRow(tableRow); + } catch (IllegalArgumentException e) { + System.err.println("Skipping row due to invalid data: " + e.getMessage()); + } + } + } + + updateResultCountLabel(); + updatePaginationControls(); + }); + } + + private void updateResultCountLabel() { + if (totalRecords == 0) { + resultCountLabel.setText("Không tìm thấy kết quả nào."); + return; + } + int startRecord = (currentPage - 1) * recordsPerPage + 1; + int endRecord = Math.min(startRecord + recordsPerPage - 1, totalRecords); + resultCountLabel.setText(String.format("Hiển thị %d đến %d của %d kết quả", startRecord, endRecord, totalRecords)); + } + + private void updatePaginationControls() { + paginationPanel.removeAll(); - firstPageButton.setPreferredSize(new Dimension(40, 30)); - prevPageButton.setPreferredSize(new Dimension(40, 30)); - nextPageButton.setPreferredSize(new Dimension(40, 30)); - lastPageButton.setPreferredSize(new Dimension(40, 30)); + JButton firstPageButton = new JButton("<<"); + firstPageButton.addActionListener(e -> fetchData(1)); + firstPageButton.setEnabled(currentPage > 1); + + JButton prevPageButton = new JButton("<"); + prevPageButton.addActionListener(e -> fetchData(currentPage - 1)); + prevPageButton.setEnabled(currentPage > 1); paginationPanel.add(firstPageButton); paginationPanel.add(prevPageButton); + + int startPage = Math.max(1, currentPage - 2); + int endPage = Math.min(totalPages, currentPage + 2); + + if (startPage > 1) { + paginationPanel.add(new JLabel("...")); + } - // Add page numbers - for (int i = 1; i <= Math.min(5, totalPages); i++) { + for (int i = startPage; i <= endPage; i++) { JButton pageButton = new JButton(String.valueOf(i)); - pageButton.setPreferredSize(new Dimension(40, 30)); if (i == currentPage) { pageButton.setBackground(new Color(51, 135, 204)); pageButton.setForeground(Color.WHITE); } final int pageNum = i; - pageButton.addActionListener(e -> goToPage(pageNum)); + pageButton.addActionListener(e -> fetchData(pageNum)); paginationPanel.add(pageButton); } - - if (totalPages > 5) { - paginationPanel.add(new JLabel("...")); - JButton lastPageNumButton = new JButton(String.valueOf(totalPages)); - lastPageNumButton.setPreferredSize(new Dimension(40, 30)); - final int lastPage = totalPages; - lastPageNumButton.addActionListener(e -> goToPage(lastPage)); - paginationPanel.add(lastPageNumButton); + + if (endPage < totalPages) { + paginationPanel.add(new JLabel("...")); } + JButton nextPageButton = new JButton(">"); + nextPageButton.addActionListener(e -> fetchData(currentPage + 1)); + nextPageButton.setEnabled(currentPage < totalPages); + + JButton lastPageButton = new JButton(">>"); + lastPageButton.addActionListener(e -> fetchData(totalPages)); + lastPageButton.setEnabled(currentPage < totalPages); + paginationPanel.add(nextPageButton); paginationPanel.add(lastPageButton); - - // Add navigation listeners - firstPageButton.addActionListener(e -> goToPage(1)); - prevPageButton.addActionListener(e -> goToPage(Math.max(1, currentPage - 1))); - nextPageButton.addActionListener(e -> goToPage(Math.min(totalPages, currentPage + 1))); - lastPageButton.addActionListener(e -> goToPage(totalPages)); - - return paginationPanel; + + paginationPanel.revalidate(); + paginationPanel.repaint(); } - private void loadFakeData() { - String[][] fakeData = { - {"1", "BN-00123", "Nguyễn Văn An", "28/07/2025", "BS. Thiên Phúc", "Viêm họng", ""}, - {"2", "BN-00122", "Trần Thị Bình", "27/07/2025", "BS. Minh Anh", "Sốt siêu vi", ""}, - {"3", "BN-00121", "Lê Hoàng Cường", "27/07/2025", "BS. Thiên Phúc", "Đau dạ dày", ""}, - {"4", "BN-00120", "Phạm Thị Dung", "26/07/2025", "BS. Minh Anh", "Khám tổng quát", ""}, - {"5", "BN-00119", "Hoàng Văn Em", "26/07/2025", "BS. Thiên Phúc", "Viêm phế quản", ""}, - {"6", "BN-00118", "Ngô Thị Phương", "25/07/2025", "BS. Hoàng Nam", "Đau đầu mãn tính", ""}, - {"7", "BN-00117", "Võ Minh Quang", "25/07/2025", "BS. Minh Anh", "Tăng huyết áp", ""}, - {"8", "BN-00116", "Đặng Thị Hoa", "24/07/2025", "BS. Thiên Phúc", "Tiểu đường type 2", ""}, - {"9", "BN-00115", "Bùi Văn Inh", "24/07/2025", "BS. Thị Lan", "Viêm gan B", ""}, - {"10", "BN-00114", "Lý Thị Kim", "23/07/2025", "BS. Minh Anh", "Khám thai 20 tuần", ""}, - {"11", "BN-00113", "Trương Văn Long", "23/07/2025", "BS. Thiên Phúc", "Viêm dạ dày", ""}, - {"12", "BN-00112", "Phan Thị Mai", "22/07/2025", "BS. Hoàng Nam", "Rối loạn lipid máu", ""}, - {"13", "BN-00111", "Nguyễn Minh Nam", "22/07/2025", "BS. Thị Lan", "Viêm khớp", ""}, - {"14", "BN-00110", "Cao Thị Oanh", "21/07/2025", "BS. Minh Anh", "Bệnh tim mạch", ""}, - {"15", "BN-00109", "Đinh Văn Phúc", "21/07/2025", "BS. Thiên Phúc", "Hen suyễn", ""}, - {"16", "BN-00108", "Lưu Thị Quỳnh", "20/07/2025", "BS. Hoàng Nam", "Viêm amidan", ""}, - {"17", "BN-00107", "Tô Văn Rạng", "20/07/2025", "BS. Thị Lan", "Đau lưng mãn tính", ""}, - {"18", "BN-00106", "Huỳnh Thị Sang", "19/07/2025", "BS. Minh Anh", "Viêm ruột thừa", ""}, - {"19", "BN-00105", "Vũ Văn Tâm", "19/07/2025", "BS. Thiên Phúc", "Suy thận mãn", ""}, - {"20", "BN-00104", "Phạm Gia Hân", "22/07/2025", "BS. Minh Anh", "Khám tổng quát", ""} - }; - - for (String[] row : fakeData) { - tableModel.addRow(row); + // --- ADD THIS NEW METHOD TO THE DataDialog CLASS --- + private void saveFiltersToLocalStorage() { + String searchTerm = searchField.getText(); + // Don't save the placeholder text + if (searchTerm.equals("Tìm theo tên bệnh nhân, mã bệnh nhân...")) { + LocalStorage.dataDialogSearchTerm = ""; + } else { + LocalStorage.dataDialogSearchTerm = searchTerm; } + + LocalStorage.dataDialogFromDate = (Date) fromDateSpinner.getValue(); + LocalStorage.dataDialogToDate = (Date) toDateSpinner.getValue(); + LocalStorage.dataDialogDoctorName = (String) doctorComboBox.getSelectedItem(); } - - private void applyFilters() { - JOptionPane.showMessageDialog(this, "Chức năng lọc dữ liệu đang được phát triển", "Thông báo", JOptionPane.INFORMATION_MESSAGE); - } - + private void clearFilters() { + // Reset UI components searchField.setText("Tìm theo tên bệnh nhân, mã bệnh nhân..."); searchField.setForeground(Color.GRAY); - doctorComboBox.setSelectedIndex(0); - fromDateSpinner.setValue(new Date()); - toDateSpinner.setValue(new Date()); - } - - private void goToPage(int page) { - currentPage = page; - // Update result count label - int startRecord = (currentPage - 1) * recordsPerPage + 1; - int endRecord = Math.min(currentPage * recordsPerPage, totalRecords); - resultCountLabel.setText(String.format("Hiển thị %d đến %d của %d kết quả", startRecord, endRecord, totalRecords)); + doctorComboBox.setSelectedIndex(0); // Defaults to "Tất cả" - // Update pagination buttons - mainPanel.remove(paginationPanel); - paginationPanel = createPaginationPanel(); - mainPanel.add(paginationPanel, BorderLayout.SOUTH); - mainPanel.revalidate(); - mainPanel.repaint(); + Date today = new Date(); + fromDateSpinner.setValue(today); + toDateSpinner.setValue(today); + + // --- ADD THIS PART TO RESET LOCALSTORAGE --- + LocalStorage.dataDialogSearchTerm = ""; + LocalStorage.dataDialogFromDate = today; + LocalStorage.dataDialogToDate = today; + LocalStorage.dataDialogDoctorName = "Tất cả"; + } + + private ImageIcon createIcon(String path, int width, int height) { + java.net.URL imgURL = getClass().getResource(path); + if (imgURL != null) { + return new ImageIcon(new ImageIcon(imgURL).getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH)); + } + System.err.println("Couldn't find file: " + path); + return null; } - // Custom renderer for action buttons class ActionButtonRenderer extends JPanel implements TableCellRenderer { - private JButton viewButton, editButton, deleteButton; - + private JButton editButton, deleteButton; + public ActionButtonRenderer() { setLayout(new FlowLayout(FlowLayout.CENTER, 2, 2)); - viewButton = new JButton("👁️"); - editButton = new JButton("✏️"); - deleteButton = new JButton("🗑️"); - - viewButton.setPreferredSize(new Dimension(30, 25)); + + + ImageIcon editIcon = createIcon("/BsK/client/ui/assets/icon/edit.png", 16, 16); + editButton = new JButton(editIcon); + ImageIcon deleteIcon = createIcon("/BsK/client/ui/assets/icon/delete.png", 16, 16); + deleteButton = new JButton(deleteIcon); + editButton.setPreferredSize(new Dimension(30, 25)); deleteButton.setPreferredSize(new Dimension(30, 25)); - - viewButton.setToolTipText("Xem"); + editButton.setToolTipText("Sửa"); deleteButton.setToolTipText("Xóa"); - - add(viewButton); + add(editButton); add(deleteButton); } - + @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (isSelected) { @@ -336,62 +471,59 @@ public class DataDialog extends JDialog { } } - // Custom editor for action buttons class ActionButtonEditor extends DefaultCellEditor { private JPanel panel; - private JButton viewButton, editButton, deleteButton; - - public ActionButtonEditor() { + private JButton editButton, deleteButton; + + public ActionButtonEditor(JDialog parentDialog) { super(new JCheckBox()); panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 2, 2)); - - viewButton = new JButton("👁️"); - editButton = new JButton("✏️"); - deleteButton = new JButton("🗑️"); - - viewButton.setPreferredSize(new Dimension(30, 25)); + + + ImageIcon editIcon = createIcon("/BsK/client/ui/assets/icon/edit.png", 16, 16); + editButton = new JButton(editIcon); + ImageIcon deleteIcon = createIcon("/BsK/client/ui/assets/icon/delete.png", 16, 16); + deleteButton = new JButton(deleteIcon); + editButton.setPreferredSize(new Dimension(30, 25)); deleteButton.setPreferredSize(new Dimension(30, 25)); - - viewButton.addActionListener(e -> { - int row = dataTable.getSelectedRow(); - String patientId = (String) tableModel.getValueAt(row, 1); - JOptionPane.showMessageDialog(DataDialog.this, "Xem thông tin bệnh nhân: " + patientId, "Xem", JOptionPane.INFORMATION_MESSAGE); - fireEditingStopped(); - }); - + editButton.addActionListener(e -> { - int row = dataTable.getSelectedRow(); - String patientId = (String) tableModel.getValueAt(row, 1); - JOptionPane.showMessageDialog(DataDialog.this, "Chỉnh sửa thông tin bệnh nhân: " + patientId, "Sửa", JOptionPane.INFORMATION_MESSAGE); + int row = dataTable.convertRowIndexToModel(dataTable.getSelectedRow()); + String checkupId = (String) tableModel.getValueAt(row, 1); + + if (checkUpPageInstance != null) { + checkUpPageInstance.loadPatientByCheckupId(checkupId); + } + + parentDialog.dispose(); fireEditingStopped(); }); - + deleteButton.addActionListener(e -> { int row = dataTable.getSelectedRow(); String patientId = (String) tableModel.getValueAt(row, 1); - int result = JOptionPane.showConfirmDialog(DataDialog.this, - "Bạn có chắc chắn muốn xóa phiếu khám của bệnh nhân " + patientId + " không?", - "Xác nhận xóa", - JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE); + int result = JOptionPane.showConfirmDialog(parentDialog, + "Bạn có chắc chắn muốn xóa phiếu khám của bệnh nhân " + patientId + " không?", + "Xác nhận xóa", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); if (result == JOptionPane.YES_OPTION) { tableModel.removeRow(row); - JOptionPane.showMessageDialog(DataDialog.this, "Đã xóa phiếu khám thành công!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(parentDialog, "Đã xóa phiếu khám thành công!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); } fireEditingStopped(); }); - - panel.add(viewButton); + panel.add(editButton); panel.add(deleteButton); } - + @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { return panel; } - + @Override public Object getCellEditorValue() { return ""; diff --git a/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java b/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java index db6dc0a..4d0e6bd 100644 --- a/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java +++ b/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java @@ -94,6 +94,7 @@ public class LoginPage extends JPanel { NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new ClinicInfoRequest()); // Request clinic info NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetDoctorGeneralInfo()); // Request doctor info NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetProvinceRequest()); // Request province info + ClientHandler.deleteListener(ErrorResponse.class); mainFrame.showPage("DashboardPage"); }); diff --git a/src/main/java/BsK/client/ui/component/NavBar.java b/src/main/java/BsK/client/ui/component/NavBar.java deleted file mode 100644 index 0519ecb..0000000 --- a/src/main/java/BsK/client/ui/component/NavBar.java +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file 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 1438736..8ddea3a 100644 --- a/src/main/java/BsK/client/ui/component/common/NavBar.java +++ b/src/main/java/BsK/client/ui/component/common/NavBar.java @@ -6,9 +6,16 @@ import BsK.client.ui.component.InfoPage.InfoDialog; import BsK.client.ui.component.SettingsPage.SettingsDialog; import BsK.client.ui.component.DataDialog.DataDialog; import BsK.client.ui.component.CheckUpPage.CheckUpPage; +import BsK.common.Error; +import BsK.common.packet.req.EmergencyRequest; import BsK.common.packet.req.LogoutRequest; import BsK.common.util.network.NetworkUtil; import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.common.packet.res.EmergencyResponse; +import BsK.common.packet.res.ErrorResponse; +import lombok.extern.slf4j.Slf4j; +import javax.swing.Icon; import javax.swing.*; import java.awt.*; @@ -16,14 +23,23 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.ActionListener; +@Slf4j public class NavBar extends JPanel { private JLabel activeNavItem = null; private final MainFrame mainFrame; + // Listener for incoming emergency alerts + private final ResponseListener emergencyResponseListener = this::handleEmergencyResponse; + private final ResponseListener errorResponseListener = this::handleErrorResponse; + public NavBar(MainFrame mainFrame, String activePageTitle) { this.mainFrame = mainFrame; + // Register the response listener for emergency alerts. + ClientHandler.addResponseListener(EmergencyResponse.class, emergencyResponseListener); + // add error response listener + ClientHandler.addResponseListener(ErrorResponse.class, errorResponseListener); this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); this.setBackground(new Color(240, 240, 240)); this.setBorder(BorderFactory.createEmptyBorder(5, 25, 5, 25)); @@ -33,7 +49,6 @@ public class NavBar extends JPanel { navItemsPanel.setBackground(this.getBackground()); navItemsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 8, 0)); navItemsPanel.setAlignmentY(Component.CENTER_ALIGNMENT); - navItemsPanel.setPreferredSize(new Dimension(800, 60)); // Center section with current page title JPanel titlePanel = new JPanel(new GridBagLayout()); @@ -45,11 +60,14 @@ public class NavBar extends JPanel { titleLabel.setForeground(new Color(60, 60, 60)); titlePanel.add(titleLabel, new GridBagConstraints()); - // Right section with user info - JPanel userPanel = new JPanel(new GridBagLayout()); + // Right section with user info and emergency button + JPanel userPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0)); userPanel.setBackground(this.getBackground()); userPanel.setAlignmentY(Component.CENTER_ALIGNMENT); - userPanel.setPreferredSize(new Dimension(200, 70)); + + // Add the Emergency button first + Component emergencyButton = createEmergencyButton(); + userPanel.add(emergencyButton); // Add welcome label with user info JPanel welcomePanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); @@ -73,17 +91,17 @@ public class NavBar extends JPanel { } }); welcomePanel.add(welcomeLabel); - userPanel.add(welcomePanel, new GridBagConstraints()); + userPanel.add(welcomePanel); String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Cài đặt", "Thông tin"}; String[] destination = {"DashboardPage", "CheckUpPage", "CheckUpPage", "SettingsPage", "InfoPage"}; String[] iconFiles = {"dashboard.png", "health-check.png", "database.png","settings.png", "info.png"}; Color[] navItemColors = { - new Color(51, 135, 204), // Darker Blue - new Color(66, 157, 21), // Darker Green - new Color(200, 138, 16), // Darker Orange - new Color(91, 37, 167), // Darker Purple - new Color(196, 27, 36) // Darker Red-Orange + new Color(51, 135, 204), // Darker Blue + new Color(66, 157, 21), // Darker Green + new Color(200, 138, 16), // Darker Orange + new Color(91, 37, 167), // Darker Purple + new Color(196, 27, 36) // Darker Red-Orange }; final Color defaultTextColor = new Color(50, 50, 50); @@ -139,13 +157,13 @@ public class NavBar extends JPanel { infoDialog.setVisible(true); return; } - + if ("SettingsPage".equals(dest)) { SettingsDialog settingsDialog = new SettingsDialog(mainFrame); settingsDialog.setVisible(true); return; } - + if ("Dữ liệu".equals(itemText)) { if (!(mainFrame.getCurrentPage() instanceof CheckUpPage)) { mainFrame.showPage("CheckUpPage"); @@ -157,7 +175,6 @@ public class NavBar extends JPanel { for (int j = 0; j < navBarItems.length; j++) { if ("Thăm khám".equals(navBarItems[j])) { Component[] components = navItemsPanel.getComponents(); - // Search through components to find the correct panel, as separators change the index for(Component comp : components) { if (comp instanceof JPanel && ((JPanel) comp).getComponentCount() > 0 && ((JPanel) comp).getComponent(0) instanceof JLabel) { JLabel potentialLabel = (JLabel) ((JPanel) comp).getComponent(0); @@ -174,7 +191,11 @@ public class NavBar extends JPanel { } } SwingUtilities.invokeLater(() -> { - DataDialog dataDialog = new DataDialog(mainFrame); + // Get the current CheckUpPage instance from the main frame + CheckUpPage checkUpPage = (CheckUpPage) mainFrame.getCurrentPage(); + + // Pass this instance to the DataDialog + DataDialog dataDialog = new DataDialog(mainFrame, checkUpPage); // <-- MODIFIED CONSTRUCTOR dataDialog.setVisible(true); }); return; @@ -208,14 +229,25 @@ public class NavBar extends JPanel { } }); navItemsPanel.add(itemPanel); - - // ADDED: Add a visual separator after the second item ("Thăm khám") - if (i == 1) { + + // --- MODIFIED SEPARATOR --- + if (i == 1) { + // Create the separator with a darker color JSeparator separator = new JSeparator(SwingConstants.VERTICAL); - separator.setPreferredSize(new Dimension(separator.getPreferredSize().width, 40)); - separator.setForeground(new Color(180, 180, 180)); // Subtle gray color - navItemsPanel.add(separator); + separator.setForeground(new Color(150, 150, 150)); + + // Set a fixed size to make it thicker and taller + separator.setPreferredSize(new Dimension(2, 45)); + + // Create a panel to hold the separator and add some horizontal padding for spacing + JPanel separatorPanel = new JPanel(); + separatorPanel.setBackground(this.getBackground()); // Match the navbar background + separatorPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); // Add 5px padding left and right + separatorPanel.add(separator); + + navItemsPanel.add(separatorPanel); } + // --- END MODIFIED SEPARATOR --- } this.add(navItemsPanel); @@ -223,7 +255,6 @@ public class NavBar extends JPanel { this.add(titlePanel); this.add(Box.createHorizontalGlue()); this.add(userPanel); - this.setPreferredSize(new Dimension(1200, 70)); } private int findNavItemIndex(String text, String[] items) { @@ -233,6 +264,67 @@ public class NavBar extends JPanel { return 0; } + private Component createEmergencyButton() { + Color emergencyColor = new Color(220, 38, 38); + RoundedPanel buttonPanel = new RoundedPanel(15, emergencyColor, true); + buttonPanel.setLayout(new BorderLayout(2, 2)); + buttonPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + buttonPanel.setPreferredSize(new Dimension(100, 55)); + buttonPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(2, 2, 2, 2), + BorderFactory.createEmptyBorder(5, 10, 5, 10) + )); + + JLabel buttonLabel = new JLabel("Khẩn cấp"); + buttonLabel.setForeground(Color.WHITE); + buttonLabel.setFont(new Font("Arial", Font.BOLD, 12)); + buttonLabel.setHorizontalAlignment(SwingConstants.CENTER); + buttonLabel.setOpaque(false); + + try { + String iconPath = "src/main/java/BsK/client/ui/assets/icon/emergency.png"; + ImageIcon originalIcon = new ImageIcon(iconPath); + Image scaledImage = originalIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH); + buttonLabel.setIcon(new ImageIcon(scaledImage)); + } catch (Exception e) { + System.err.println("Error loading icon: emergency.png. Using text fallback."); + buttonLabel.setText("Khẩn cấp"); + } + + buttonLabel.setVerticalTextPosition(SwingConstants.BOTTOM); + buttonLabel.setHorizontalTextPosition(SwingConstants.CENTER); + buttonPanel.add(buttonLabel, BorderLayout.CENTER); + + buttonPanel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int response = JOptionPane.showConfirmDialog( + mainFrame, + "Bạn có chắc chắn muốn kích hoạt chế độ khẩn cấp không?", + "XÁC NHẬN KHẨN CẤP", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ); + if (response == JOptionPane.YES_OPTION) { + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new EmergencyRequest()); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + buttonPanel.setBackground(emergencyColor.darker()); + } + + @Override + public void mouseExited(MouseEvent e) { + buttonPanel.setBackground(emergencyColor); + } + }); + + return buttonPanel; + } + private void showUserMenu(JLabel welcomeLabel) { JPopupMenu popupMenu = new JPopupMenu(); popupMenu.setBorder(BorderFactory.createEmptyBorder()); @@ -268,4 +360,79 @@ public class NavBar extends JPanel { item.addActionListener(action); return item; } + + /** + * Handles the incoming EmergencyResponse. This version is silent and uses a manual + * dialog creation to ensure the custom close button works correctly. + * @param response The emergency response packet from the server. + */ + private void handleEmergencyResponse(EmergencyResponse response) { + SwingUtilities.invokeLater(() -> { + log.warn("Received EMERGENCY alert from: {}", response.getSenderName()); + + // Create enhanced visual components for the dialog + String htmlMessage = "" + + "

CẢNH BÁO KHẨN CẤP!

" + + "

Nhận được tín hiệu khẩn cấp từ người dùng:

" + + "

" + response.getSenderName() + "

" + + "

Vui lòng kiểm tra và hỗ trợ ngay lập tức!

" + + ""; + JLabel messageLabel = new JLabel(htmlMessage); + + JCheckBox acknowledgementCheck = new JCheckBox("Tôi xác nhận đã đọc và hiểu rõ cảnh báo này."); + acknowledgementCheck.setFont(new Font("Arial", Font.ITALIC, 12)); + acknowledgementCheck.setOpaque(false); + + JPanel contentPanel = new JPanel(new BorderLayout(0, 15)); + contentPanel.setOpaque(false); + contentPanel.add(messageLabel, BorderLayout.CENTER); + contentPanel.add(acknowledgementCheck, BorderLayout.SOUTH); + + JPanel dialogPanel = new JPanel(new BorderLayout()); + dialogPanel.setBackground(new Color(255, 220, 220)); + dialogPanel.setBorder(BorderFactory.createLineBorder(Color.RED, 3)); + dialogPanel.add(contentPanel, BorderLayout.CENTER); + + JButton confirmButton = new JButton("ĐÃ XỬ LÝ"); + confirmButton.setFont(new Font("Arial", Font.BOLD, 14)); + confirmButton.setEnabled(false); + + acknowledgementCheck.addActionListener(e -> confirmButton.setEnabled(acknowledgementCheck.isSelected())); + + // --- CORRECTED DIALOG CREATION --- + // 1. Create a JOptionPane instance with our custom panel and button. + JOptionPane optionPane = new JOptionPane( + dialogPanel, + JOptionPane.ERROR_MESSAGE, + JOptionPane.DEFAULT_OPTION, + null, + new Object[]{confirmButton}, // Array of options + confirmButton); + + // 2. Create a JDialog from the JOptionPane. + final JDialog dialog = optionPane.createDialog(mainFrame, "🚨🚨 TÍN HIỆU KHẨN CẤP 🚨🚨"); + + // 3. Add an ActionListener to our custom button to close the dialog. + confirmButton.addActionListener(e -> dialog.dispose()); + + // 4. Show the dialog. + dialog.setVisible(true); + }); + } + + private void handleErrorResponse(ErrorResponse response) { + log.error("Error response: {}", response.getError()); + SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(mainFrame, + response.getError().toString(), "Lỗi", JOptionPane.ERROR_MESSAGE)); + } + + /** + * Cleans up resources, specifically unregistering network listeners. + * This should be called when the NavBar is no longer needed (e.g., on logout). + */ + public void cleanup() { + ClientHandler.deleteListener(EmergencyResponse.class, emergencyResponseListener); + ClientHandler.deleteListener(ErrorResponse.class, errorResponseListener); + log.info("EmergencyResponse listener removed from NavBar."); + } } \ No newline at end of file diff --git a/src/main/java/BsK/common/Error.java b/src/main/java/BsK/common/Error.java new file mode 100644 index 0000000..43164b5 --- /dev/null +++ b/src/main/java/BsK/common/Error.java @@ -0,0 +1,11 @@ +package BsK.common; + +public enum Error { + SUCCESS, + UNKNOWN, + USERNAME_NOT_FOUND, + PASSWORD_INCORRECT, + USER_ALREADY_EXISTS,INVALID_CREDENTIALS, + SQL_EXCEPTION, + ACCESS_DENIED +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/EmergencyRequest.java b/src/main/java/BsK/common/packet/req/EmergencyRequest.java new file mode 100644 index 0000000..7c23514 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/EmergencyRequest.java @@ -0,0 +1,11 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EmergencyRequest implements Packet { + +} diff --git a/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java b/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java new file mode 100644 index 0000000..3daeb79 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/GetCheckupDataRequest.java @@ -0,0 +1,18 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetCheckupDataRequest implements Packet { + private String searchTerm; + private Long fromDate; // Use Long for timestamp + private Long toDate; // Use Long for timestamp + private Integer doctorId; + private int page = 1; + private int pageSize = 20; +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/res/EmergencyResponse.java b/src/main/java/BsK/common/packet/res/EmergencyResponse.java new file mode 100644 index 0000000..4e27660 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/EmergencyResponse.java @@ -0,0 +1,12 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EmergencyResponse implements Packet { + String senderName; +} diff --git a/src/main/java/BsK/common/packet/res/ErrorResponse.java b/src/main/java/BsK/common/packet/res/ErrorResponse.java index 3c78481..bd32144 100644 --- a/src/main/java/BsK/common/packet/res/ErrorResponse.java +++ b/src/main/java/BsK/common/packet/res/ErrorResponse.java @@ -1,6 +1,7 @@ package BsK.common.packet.res; import BsK.common.packet.Packet; +import BsK.common.Error; import lombok.AllArgsConstructor; import lombok.Data; @@ -11,15 +12,29 @@ public class ErrorResponse implements Packet { Error error; public String getError() { - return error.toString(); + if (error.equals(Error.ACCESS_DENIED)) { + return "Bạn không có quyền truy cập tài nguyên này"; + } + if (error.equals(Error.SQL_EXCEPTION)) { + return "Lỗi kết nối cơ sở dữ liệu, vui lòng kiểm tra lại kết nối và khởi động lại ứng dụng"; + } + if (error.equals(Error.UNKNOWN)) { + return "Lỗi không xác định, vui lòng liên hệ với quản trị viên"; + } + if (error.equals(Error.USERNAME_NOT_FOUND)) { + return "Tên đăng nhập không tồn tại"; + } + if (error.equals(Error.PASSWORD_INCORRECT)) { + return "Mật khẩu không chính xác, vui lòng kiểm tra lại mật khẩu"; + } + if (error.equals(Error.USER_ALREADY_EXISTS)) { + return "Tên đăng nhập đã tồn tại, vui lòng chọn tên đăng nhập khác"; + } + if (error.equals(Error.INVALID_CREDENTIALS)) { + return "Tên đăng nhập hoặc mật khẩu không chính xác, vui lòng kiểm tra lại"; + } + return error.toString(); } - public enum Error { - SUCCESS, - UNKNOWN, - USERNAME_NOT_FOUND, - PASSWORD_INCORRECT, - USER_ALREADY_EXISTS,INVALID_CREDENTIALS, - SQL_EXCEPTION, - } + } diff --git a/src/main/java/BsK/common/packet/res/GetCheckupDataResponse.java b/src/main/java/BsK/common/packet/res/GetCheckupDataResponse.java new file mode 100644 index 0000000..8d90cf7 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/GetCheckupDataResponse.java @@ -0,0 +1,17 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetCheckupDataResponse implements Packet { + private String[][] checkupData; + private int totalRecords; + private int currentPage; + private int totalPages; + private int pageSize; +} \ 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 5f468bf..7232908 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -11,6 +11,7 @@ import BsK.server.Server; import BsK.server.ServerDashboard; import BsK.server.network.manager.SessionManager; import BsK.server.network.util.UserUtil; +import BsK.server.network.entity.Role; import BsK.server.network.entity.User; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -1350,7 +1351,168 @@ public class ServerHandler extends SimpleChannelInboundHandler params = new java.util.ArrayList<>(); + boolean hasWhere = false; + + if (getCheckupDataRequest.getSearchTerm() != null && !getCheckupDataRequest.getSearchTerm().trim().isEmpty()) { + whereClause.append("WHERE (LOWER(c.customer_first_name) LIKE ? OR LOWER(c.customer_last_name) LIKE ? OR " + + "LOWER(CONCAT(c.customer_last_name, ' ', c.customer_first_name)) LIKE ? OR c.customer_number LIKE ?)"); + String searchTerm = "%" + getCheckupDataRequest.getSearchTerm().toLowerCase().trim() + "%"; + params.add(searchTerm); params.add(searchTerm); params.add(searchTerm); params.add(searchTerm); + hasWhere = true; + } + + if (getCheckupDataRequest.getFromDate() != null && getCheckupDataRequest.getToDate() != null) { + whereClause.append(hasWhere ? " AND " : "WHERE ").append("a.checkup_date BETWEEN ? AND ?"); + params.add(getCheckupDataRequest.getFromDate()); + params.add(getCheckupDataRequest.getToDate()); + hasWhere = true; + } + + if (getCheckupDataRequest.getDoctorId() != null && getCheckupDataRequest.getDoctorId() > 0) { + whereClause.append(hasWhere ? " AND " : "WHERE ").append("a.doctor_id = ?"); + params.add(getCheckupDataRequest.getDoctorId()); + } + + // --- 3. Count Total Records (Unchanged) --- + String countQuery = "SELECT COUNT(*) " + fromAndJoin + whereClause; + PreparedStatement countStmt = Server.connection.prepareStatement(countQuery); + for (int i = 0; i < params.size(); i++) { + countStmt.setObject(i + 1, params.get(i)); + } + ResultSet countRs = countStmt.executeQuery(); + int totalRecords = 0; + if (countRs.next()) totalRecords = countRs.getInt(1); + countRs.close(); + countStmt.close(); + + // --- 4. Main Data Query with Pagination (Unchanged) --- + int pageSize = getCheckupDataRequest.getPageSize(); + int currentPage = getCheckupDataRequest.getPage(); + int totalPages = (int) Math.ceil((double) totalRecords / pageSize); + int offset = (currentPage - 1) * pageSize; + + String finalQuery = baseSelect + fromAndJoin + whereClause + " ORDER BY a.checkup_date DESC, a.checkup_id DESC LIMIT ? OFFSET ?"; + PreparedStatement stmt = Server.connection.prepareStatement(finalQuery); + int paramIndex = 1; + for (Object param : params) stmt.setObject(paramIndex++, param); + stmt.setInt(paramIndex++, pageSize); + stmt.setInt(paramIndex, offset); + ResultSet rs = stmt.executeQuery(); + + // --- 5. Process ResultSet and Send Response (UPDATED to include all fields) --- + java.util.List resultList = new java.util.ArrayList<>(); + while (rs.next()) { + // Retrieve all 25 fields for the Patient DTO + String checkupId = rs.getString("checkup_id"); + String checkupDate = String.valueOf(rs.getLong("checkup_date")); + String customerLastName = rs.getString("customer_last_name"); + String customerFirstName = rs.getString("customer_first_name"); + String doctorName = "BS. " + rs.getString("doctor_last_name") + " " + rs.getString("doctor_first_name"); + String suggestion = rs.getString("suggestion"); + String diagnosis = rs.getString("diagnosis"); + String notes = rs.getString("notes"); + String status = rs.getString("status"); + String customerId = rs.getString("customer_id"); + String customerNumber = rs.getString("customer_number"); + String customerAddress = rs.getString("customer_address"); + String customerWeight = String.valueOf(rs.getBigDecimal("customer_weight")); + String customerHeight = String.valueOf(rs.getBigDecimal("customer_height")); + String customerGender = rs.getString("customer_gender"); + String customerDob = String.valueOf(rs.getLong("customer_dob")); + String checkupType = rs.getString("checkup_type"); + String conclusion = rs.getString("conclusion"); + String reCheckupDate = String.valueOf(rs.getLong("reCheckupDate")); + String cccdDdcn = rs.getString("cccd_ddcn"); + String heartBeat = String.valueOf(rs.getInt("heart_beat")); + String bloodPressure = rs.getString("blood_pressure"); + String driveUrl = rs.getString("drive_url"); + String doctorUltrasoundId = String.valueOf(rs.getInt("doctor_ultrasound_id")); + String queueNumber = String.valueOf(rs.getInt("queue_number")); + + // Add the full data array, handling nulls to prevent client-side errors + resultList.add(new String[]{ + checkupId != null ? checkupId : "", + checkupDate != null ? checkupDate : "0", + customerLastName != null ? customerLastName : "", + customerFirstName != null ? customerFirstName : "", + doctorName, // Already constructed + suggestion != null ? suggestion : "", + diagnosis != null ? diagnosis : "", + notes != null ? notes : "", + status != null ? status : "", + customerId != null ? customerId : "", + customerNumber != null ? customerNumber : "", + customerAddress != null ? customerAddress : "", + customerWeight != null ? customerWeight : "0.0", + customerHeight != null ? customerHeight : "0.0", + customerGender != null ? customerGender : "", + customerDob != null ? customerDob : "0", + checkupType != null ? checkupType : "", + conclusion != null ? conclusion : "", + reCheckupDate != null ? reCheckupDate : "0", + cccdDdcn != null ? cccdDdcn : "", + heartBeat != null ? heartBeat : "0", + bloodPressure != null ? bloodPressure : "0/0", + driveUrl != null ? driveUrl : "", + doctorUltrasoundId != null ? doctorUltrasoundId : "0", + queueNumber != null ? queueNumber : "0" + }); + } + + String[][] resultArray = resultList.toArray(new String[0][]); + UserUtil.sendPacket(currentUser.getSessionId(), new GetCheckupDataResponse(resultArray, totalRecords, currentPage, totalPages, pageSize)); + + rs.close(); + stmt.close(); + } catch (SQLException e) { + log.error("Error processing GetCheckupDataRequest", e); + } + } } + } private void getRecheckUpList(int sessionId) {