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 0000000..c5b58c0 Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/view.png differ 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) {