mirror of
https://github.com/tiennm99/BSK-All-in-One-Clinic-Management-System.git
synced 2026-10-16 16:15:08 +00:00
[update] Revamp CheckUpPage and DashboardPage for enhanced patient management. Update table structure to include queue number and status, improving data clarity. Implement real-time updates for patient counts and queue status, optimizing user experience. Refactor UI components for better organization and responsiveness, ensuring a cohesive interface across the application.
This commit is contained in:
1 parent
0ac1516e1b
commit
1d66cb16af
12 files changed
+1036
-428
No files matched your search
Binary file not shown.
@@ -296,18 +296,13 @@ public class CheckUpPage extends JPanel {
|
||||
|
||||
for (int i = 0; i < patients.size(); i++) {
|
||||
Patient p = patients.get(i);
|
||||
tableData[i][0] = p.getCheckupId();
|
||||
|
||||
// Convert checkup date to display format using utility function
|
||||
tableData[i][1] = DateUtils.convertToDisplayFormat(p.getCheckupDate());
|
||||
|
||||
// Convert DOB to display format using utility function
|
||||
tableData[i][2] = DateUtils.convertToDisplayFormat(p.getCustomerDob());
|
||||
|
||||
tableData[i][3] = p.getCustomerLastName();
|
||||
tableData[i][4] = p.getCustomerFirstName();
|
||||
tableData[i][5] = p.getDoctorName();
|
||||
tableData[i][6] = p.getCheckupType();
|
||||
tableData[i][0] = p.getQueueNumber(); // STT
|
||||
tableData[i][1] = p.getCheckupId(); // Mã Khám
|
||||
tableData[i][2] = p.getCustomerLastName() + " " + p.getCustomerFirstName(); // Họ và Tên
|
||||
tableData[i][3] = String.valueOf(DateUtils.extractYearFromTimestamp(p.getCustomerDob())); // Năm sinh
|
||||
tableData[i][4] = p.getDoctorName(); // Bác Sĩ
|
||||
tableData[i][5] = p.getCheckupType(); // Loại khám
|
||||
tableData[i][6] = p.getStatus(); // Trạng thái
|
||||
}
|
||||
return tableData;
|
||||
}
|
||||
@@ -601,7 +596,7 @@ public class CheckUpPage extends JPanel {
|
||||
|
||||
// Row 2: ID and Phone
|
||||
gbcPatient.gridx = 0; gbcPatient.gridy = 2; gbcPatient.weightx = 0.1;
|
||||
JLabel idLabel = new JLabel("Mã BN", SwingConstants.RIGHT);
|
||||
JLabel idLabel = new JLabel("Mã Khám", SwingConstants.RIGHT);
|
||||
idLabel.setFont(labelFont);
|
||||
patientInfoInnerPanel.add(idLabel, gbcPatient);
|
||||
|
||||
@@ -4171,7 +4166,7 @@ public class CheckUpPage extends JPanel {
|
||||
JPanel filterPanel = createFilterPanel();
|
||||
add(filterPanel, BorderLayout.NORTH);
|
||||
|
||||
String[] columns = {"Mã khám bệnh", "Ngày khám", "Ngày sinh", "Họ", "Tên", "Bác Sĩ", "Loại khám"};
|
||||
String[] columns = {"STT", "Mã Khám", "Họ và Tên", "Năm sinh", "Bác Sĩ", "Loại khám", "Trạng thái"};
|
||||
queueTableModel = new DefaultTableModel(preprocessPatientDataForTable(filteredPatients), columns) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) { return false; }
|
||||
@@ -4183,19 +4178,15 @@ public class CheckUpPage extends JPanel {
|
||||
queueTable.setRowHeight(35);
|
||||
queueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
// Create custom row sorter with date comparator for proper date sorting
|
||||
// Create a row sorter for the table
|
||||
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(queueTableModel);
|
||||
|
||||
// Set up date comparator for column 1 (Ngày khám) and column 2 (Ngày sinh)
|
||||
sorter.setComparator(1, createDateComparator()); // Checkup Date
|
||||
sorter.setComparator(2, createDateComparator()); // Date of Birth
|
||||
|
||||
queueTable.setRowSorter(sorter);
|
||||
|
||||
// Sort by "Ngày khám" column in ascending order (earliest dates first)
|
||||
// Sort by "STT" (queue number) column in ascending order
|
||||
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
|
||||
sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING));
|
||||
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
|
||||
sorter.setSortKeys(sortKeys);
|
||||
|
||||
queueTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
@@ -4378,21 +4369,16 @@ public class CheckUpPage extends JPanel {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Update table with filtered data
|
||||
String[] columns = {"Mã khám bệnh", "Ngày khám", "Ngày sinh", "Họ", "Tên", "Bác Sĩ", "Loại khám"};
|
||||
String[] columns = {"STT", "Mã Khám", "Họ và Tên", "Năm sinh", "Bác Sĩ", "Loại khám", "Trạng thái"};
|
||||
queueTableModel.setDataVector(preprocessPatientDataForTable(filteredPatients), columns);
|
||||
|
||||
// Recreate row sorter with date comparator after data update
|
||||
// Recreate row sorter for the table after data update
|
||||
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(queueTableModel);
|
||||
|
||||
// Set up date comparator for column 1 (Ngày khám) and column 2 (Ngày sinh)
|
||||
sorter.setComparator(1, createDateComparator()); // Checkup Date
|
||||
sorter.setComparator(2, createDateComparator()); // Date of Birth
|
||||
|
||||
queueTable.setRowSorter(sorter);
|
||||
|
||||
// Apply sorting by checkup date
|
||||
// Apply sorting by "STT" (queue number)
|
||||
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
|
||||
sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING));
|
||||
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
|
||||
sorter.setSortKeys(sortKeys);
|
||||
|
||||
// Auto-select first row after applying filters
|
||||
|
||||
@@ -6,45 +6,112 @@ import BsK.client.ui.component.common.NavBar;
|
||||
import BsK.client.ui.component.common.RoundedButtonUI;
|
||||
import BsK.client.ui.component.common.RoundedPanel;
|
||||
import BsK.client.ui.component.common.AddDialog.AddDialog;
|
||||
import BsK.client.ui.component.DashboardPage.RecheckUpDialog.RecheckUpDialog;
|
||||
import BsK.client.network.handler.ClientHandler;
|
||||
import BsK.client.network.handler.ResponseListener;
|
||||
import BsK.common.entity.Patient;
|
||||
import BsK.common.packet.req.GetCheckUpQueueUpdateRequest;
|
||||
import BsK.common.packet.res.GetCheckUpQueueUpdateResponse;
|
||||
import BsK.common.packet.res.TodayPatientCountResponse;
|
||||
import BsK.common.packet.res.RecheckCountResponse;
|
||||
import BsK.common.util.network.NetworkUtil;
|
||||
import BsK.common.util.date.DateUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class DashboardPage extends JPanel {
|
||||
private final MainFrame mainFrame;
|
||||
|
||||
|
||||
private JLabel todayPatientsLabel;
|
||||
private JLabel waitingPatientsLabel;
|
||||
private JLabel recheckPatientsLabel;
|
||||
private JLabel totalPatientsLabel;
|
||||
private JLabel dateTimeLabel;
|
||||
private JTable currentQueueTable;
|
||||
private JLabel lastUpdateLabel;
|
||||
private Timer realTimeClockTimer;
|
||||
|
||||
private final List<Patient> patientQueue = new ArrayList<>();
|
||||
|
||||
// --- LISTENERS ---
|
||||
private final ResponseListener<GetCheckUpQueueUpdateResponse> checkUpQueueUpdateListener = this::handleGetCheckUpQueueUpdateResponse;
|
||||
private final ResponseListener<TodayPatientCountResponse> todayPatientCountListener = this::handleTodayPatientCountResponse;
|
||||
private final ResponseListener<RecheckCountResponse> recheckCountListener = this::handleRecheckCountResponse;
|
||||
|
||||
private void handleGetCheckUpQueueUpdateResponse(GetCheckUpQueueUpdateResponse response) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
log.info("Received dashboard queue update from server.");
|
||||
this.patientQueue.clear();
|
||||
if (response.getQueue() != null) {
|
||||
for (String[] patientData : response.getQueue()) {
|
||||
try {
|
||||
this.patientQueue.add(new Patient(patientData));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Received malformed patient data for queue: {}", (Object) patientData, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateQueueTable();
|
||||
lastUpdateLabel.setText("Cập nhật lần cuối: " + getCurrentTime());
|
||||
flashComponent(lastUpdateLabel, new Color(16, 185, 129));
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTodayPatientCountResponse(TodayPatientCountResponse response) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
log.info("Received today's patient count: {}", response.getTotalPatientsToday());
|
||||
if (todayPatientsLabel != null) {
|
||||
todayPatientsLabel.setText(String.valueOf(response.getTotalPatientsToday()));
|
||||
} else {
|
||||
log.warn("todayPatientsLabel is null, cannot update UI.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleRecheckCountResponse(RecheckCountResponse response) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
log.info("Received recheck count: {}", response.getRecheckCount());
|
||||
if (recheckPatientsLabel != null) {
|
||||
recheckPatientsLabel.setText(String.valueOf(response.getRecheckCount()));
|
||||
} else {
|
||||
log.warn("recheckPatientsLabel is null, cannot update UI.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void flashComponent(JComponent component, Color flashColor) {
|
||||
final Color originalColor = component.getForeground();
|
||||
component.setForeground(flashColor);
|
||||
Timer flashTimer = new Timer(1000, e -> component.setForeground(originalColor));
|
||||
flashTimer.setRepeats(false);
|
||||
flashTimer.start();
|
||||
}
|
||||
|
||||
public DashboardPage(MainFrame mainFrame) {
|
||||
this.mainFrame = mainFrame;
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(new Color(245, 247, 250));
|
||||
|
||||
// --- Navigation Bar ---
|
||||
NavBar navBar = new NavBar(mainFrame, "Thống kê");
|
||||
add(navBar, BorderLayout.NORTH);
|
||||
|
||||
// --- Main Content Panel ---
|
||||
JPanel mainContent = new JPanel();
|
||||
mainContent.setLayout(new BorderLayout());
|
||||
mainContent.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
|
||||
mainContent.setOpaque(false);
|
||||
|
||||
// Create main sections
|
||||
JPanel topSection = createTopSection();
|
||||
JPanel centerSection = createCenterSection();
|
||||
|
||||
@@ -53,24 +120,42 @@ public class DashboardPage extends JPanel {
|
||||
|
||||
add(mainContent, BorderLayout.CENTER);
|
||||
|
||||
// Load initial data
|
||||
loadDashboardData();
|
||||
startRealTimeClock();
|
||||
|
||||
addAncestorListener(new AncestorListener() {
|
||||
@Override
|
||||
public void ancestorAdded(AncestorEvent event) {
|
||||
ClientHandler.addResponseListener(GetCheckUpQueueUpdateResponse.class, checkUpQueueUpdateListener);
|
||||
ClientHandler.addResponseListener(TodayPatientCountResponse.class, todayPatientCountListener);
|
||||
ClientHandler.addResponseListener(RecheckCountResponse.class, recheckCountListener);
|
||||
log.info("DashboardPage listeners registered. Requesting initial data.");
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetCheckUpQueueUpdateRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent event) {
|
||||
ClientHandler.deleteListener(GetCheckUpQueueUpdateResponse.class, checkUpQueueUpdateListener);
|
||||
ClientHandler.deleteListener(TodayPatientCountResponse.class, todayPatientCountListener);
|
||||
log.info("DashboardPage listeners removed.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) { /* Do nothing */ }
|
||||
});
|
||||
}
|
||||
|
||||
private void startRealTimeClock() {
|
||||
realTimeClockTimer = new Timer(1000, e -> updateDateTime());
|
||||
realTimeClockTimer.start();
|
||||
}
|
||||
|
||||
private JPanel createTopSection() {
|
||||
JPanel topSection = new JPanel(new BorderLayout());
|
||||
topSection.setOpaque(false);
|
||||
topSection.setBorder(BorderFactory.createEmptyBorder(0, 0, 30, 0));
|
||||
|
||||
// Welcome header
|
||||
JPanel headerPanel = createHeaderPanel();
|
||||
|
||||
// Patient metrics cards
|
||||
JPanel metricsPanel = createMetricsPanel();
|
||||
|
||||
topSection.add(headerPanel, BorderLayout.NORTH);
|
||||
topSection.add(metricsPanel, BorderLayout.CENTER);
|
||||
|
||||
topSection.add(createHeaderPanel(), BorderLayout.NORTH);
|
||||
topSection.add(createMetricsPanel(), BorderLayout.CENTER);
|
||||
return topSection;
|
||||
}
|
||||
|
||||
@@ -78,15 +163,12 @@ public class DashboardPage extends JPanel {
|
||||
JPanel headerPanel = new JPanel(new BorderLayout());
|
||||
headerPanel.setOpaque(false);
|
||||
headerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
|
||||
|
||||
JLabel welcomeLabel = new JLabel("Chào mừng, " + LocalStorage.username);
|
||||
welcomeLabel.setFont(new Font("Segoe UI", Font.BOLD, 28));
|
||||
welcomeLabel.setForeground(new Color(44, 82, 130));
|
||||
|
||||
lastUpdateLabel = new JLabel("Cập nhật lần cuối: " + getCurrentTime());
|
||||
lastUpdateLabel.setFont(new Font("Segoe UI", Font.PLAIN, 14));
|
||||
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));
|
||||
@@ -95,121 +177,110 @@ public class DashboardPage extends JPanel {
|
||||
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);
|
||||
|
||||
// Patient metrics cards with specific titles
|
||||
String[] cardTitles = {"Bệnh nhân trong ngày", "Đang đợi khám", "Cần tái khám", "Tổng bệnh nhân"};
|
||||
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"};
|
||||
Color[] cardColors = {
|
||||
new Color(59, 130, 246), // Blue - Today's patients
|
||||
new Color(245, 158, 11), // Orange - Waiting patients
|
||||
new Color(139, 92, 246), // Purple - Recheck patients
|
||||
new Color(16, 185, 129) // Green - Total patients
|
||||
new Color(59, 130, 246), new Color(245, 158, 11),
|
||||
new Color(139, 92, 246), new Color(16, 185, 129)
|
||||
};
|
||||
String[] cardIcons = {"👥", "⏳", "🔄", "📊"};
|
||||
|
||||
JLabel[] valueLabels = new JLabel[4];
|
||||
String[] cardIcons = {"👥", "⏳", "🔄", "📅"};
|
||||
JLabel[] valueLabels = new JLabel[3];
|
||||
|
||||
for (int i = 0; i < cardTitles.length; i++) {
|
||||
RoundedPanel card = createMetricCard(cardTitles[i], cardColors[i], cardIcons[i]);
|
||||
|
||||
// Store references to value labels for updating
|
||||
Component[] components = card.getComponents();
|
||||
for (Component comp : components) {
|
||||
if (comp instanceof JPanel) {
|
||||
Component[] subComps = ((JPanel) comp).getComponents();
|
||||
for (Component subComp : subComps) {
|
||||
if (subComp instanceof JLabel && ((JLabel) subComp).getFont().getSize() == 32) {
|
||||
valueLabels[i] = (JLabel) subComp;
|
||||
break;
|
||||
}
|
||||
if (i == 3) {
|
||||
metricsPanel.add(createDateTimeCard(cardTitles[i], cardColors[i], cardIcons[i]));
|
||||
} 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) {
|
||||
valueLabels[i] = (JLabel) comp;
|
||||
break; // Found the label, exit the inner loop
|
||||
}
|
||||
}
|
||||
// --- MODIFIED SECTION END ---
|
||||
|
||||
metricsPanel.add(card);
|
||||
}
|
||||
|
||||
metricsPanel.add(card);
|
||||
}
|
||||
|
||||
// Store references for updating data
|
||||
todayPatientsLabel = valueLabels[0];
|
||||
waitingPatientsLabel = valueLabels[1];
|
||||
recheckPatientsLabel = valueLabels[2];
|
||||
totalPatientsLabel = valueLabels[3];
|
||||
|
||||
return metricsPanel;
|
||||
}
|
||||
|
||||
private RoundedPanel createDateTimeCard(String title, Color color, String icon) {
|
||||
RoundedPanel card = new RoundedPanel(15, color, true);
|
||||
card.setLayout(new BorderLayout());
|
||||
card.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
JPanel topPanel = new JPanel(new BorderLayout());
|
||||
topPanel.setOpaque(false);
|
||||
JLabel iconLabel = new JLabel(icon);
|
||||
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 30));
|
||||
JLabel titleLabel = new JLabel(title);
|
||||
titleLabel.setForeground(Color.WHITE);
|
||||
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
topPanel.add(iconLabel, BorderLayout.WEST);
|
||||
topPanel.add(titleLabel, BorderLayout.CENTER);
|
||||
dateTimeLabel = new JLabel();
|
||||
dateTimeLabel.setForeground(Color.WHITE);
|
||||
dateTimeLabel.setFont(new Font("Segoe UI", Font.BOLD, 22));
|
||||
dateTimeLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
card.add(topPanel, BorderLayout.NORTH);
|
||||
card.add(dateTimeLabel, BorderLayout.CENTER);
|
||||
card.addMouseListener(new MouseAdapter() {
|
||||
public void mouseEntered(MouseEvent e) { card.setBackground(color.darker()); }
|
||||
public void mouseExited(MouseEvent e) { card.setBackground(color); }
|
||||
});
|
||||
return card;
|
||||
}
|
||||
|
||||
private RoundedPanel createMetricCard(String title, Color color, String icon) {
|
||||
RoundedPanel card = new RoundedPanel(15, color, true);
|
||||
card.setLayout(new BorderLayout());
|
||||
card.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
card.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
|
||||
// Icon and title panel
|
||||
JPanel topPanel = new JPanel(new BorderLayout());
|
||||
topPanel.setOpaque(false);
|
||||
|
||||
JLabel iconLabel = new JLabel(icon);
|
||||
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 24));
|
||||
|
||||
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 30));
|
||||
JLabel titleLabel = new JLabel(title);
|
||||
titleLabel.setForeground(Color.WHITE);
|
||||
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
|
||||
topPanel.add(iconLabel, BorderLayout.WEST);
|
||||
topPanel.add(titleLabel, BorderLayout.CENTER);
|
||||
|
||||
// Value label
|
||||
JLabel valueLabel = new JLabel("--");
|
||||
valueLabel.setForeground(Color.WHITE);
|
||||
valueLabel.setFont(new Font("Segoe UI", Font.BOLD, 32));
|
||||
valueLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
card.add(topPanel, BorderLayout.NORTH);
|
||||
card.add(valueLabel, BorderLayout.CENTER);
|
||||
|
||||
// Add hover effect
|
||||
card.add(valueLabel, BorderLayout.CENTER); // valueLabel is a direct child
|
||||
card.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
card.setBackground(color.darker());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
card.setBackground(color);
|
||||
}
|
||||
public void mouseEntered(MouseEvent e) { card.setBackground(color.darker()); }
|
||||
public void mouseExited(MouseEvent e) { card.setBackground(color); }
|
||||
});
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private JPanel createCenterSection() {
|
||||
JPanel centerSection = new JPanel(new GridLayout(1, 2, 20, 0));
|
||||
centerSection.setOpaque(false);
|
||||
|
||||
// Current queue panel (left side)
|
||||
JPanel queuePanel = createCurrentQueuePanel();
|
||||
|
||||
// Action buttons panel (right side)
|
||||
JPanel actionsPanel = createActionButtonsPanel();
|
||||
|
||||
centerSection.add(queuePanel);
|
||||
centerSection.add(actionsPanel);
|
||||
|
||||
centerSection.add(createCurrentQueuePanel());
|
||||
centerSection.add(createActionButtonsPanel());
|
||||
return centerSection;
|
||||
}
|
||||
|
||||
@@ -217,76 +288,67 @@ public class DashboardPage extends JPanel {
|
||||
RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false);
|
||||
panel.setLayout(new BorderLayout());
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
JLabel titleLabel = new JLabel("Hàng đợi hiện tại");
|
||||
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18));
|
||||
titleLabel.setForeground(new Color(31, 41, 55));
|
||||
|
||||
// Table for current queue
|
||||
String[] columns = {"STT", "Mã BN", "Họ và Tên", "Loại khám", "Trạng thái"};
|
||||
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"};
|
||||
DefaultTableModel tableModel = new DefaultTableModel(columns, 0) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return false;
|
||||
}
|
||||
public boolean isCellEditable(int row, int column) { return false; }
|
||||
};
|
||||
|
||||
currentQueueTable = new JTable(tableModel);
|
||||
currentQueueTable.setRowHeight(35);
|
||||
currentQueueTable.getTableHeader().setFont(new Font("Segoe UI", Font.BOLD, 12));
|
||||
currentQueueTable.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
currentQueueTable.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
currentQueueTable.setSelectionBackground(new Color(219, 234, 254));
|
||||
currentQueueTable.setGridColor(new Color(229, 231, 235));
|
||||
|
||||
JScrollPane tableScroll = new JScrollPane(currentQueueTable);
|
||||
tableScroll.setBorder(BorderFactory.createEmptyBorder());
|
||||
tableScroll.getViewport().setBackground(Color.WHITE);
|
||||
|
||||
panel.add(titleLabel, BorderLayout.NORTH);
|
||||
panel.add(Box.createVerticalStrut(15), BorderLayout.NORTH);
|
||||
panel.add(tableScroll, BorderLayout.CENTER);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private JPanel createActionButtonsPanel() {
|
||||
RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false);
|
||||
panel.setLayout(new BorderLayout());
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
JLabel titleLabel = new JLabel("Thao tác nhanh");
|
||||
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18));
|
||||
titleLabel.setForeground(new Color(31, 41, 55));
|
||||
|
||||
JPanel actionsGrid = new JPanel(new GridLayout(2, 2, 15, 15));
|
||||
actionsGrid.setOpaque(false);
|
||||
actionsGrid.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
|
||||
|
||||
// 4 specific action buttons
|
||||
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 = {"➕", "📞", "📊", "🚨"};
|
||||
Color[] actionColors = {
|
||||
new Color(34, 197, 94), // Green - Add patient
|
||||
new Color(59, 130, 246), // Blue - Call recheck patient
|
||||
new Color(168, 85, 247), // Purple - Daily report
|
||||
new Color(239, 68, 68) // Red - Emergency
|
||||
new Color(34, 197, 94), new Color(59, 130, 246),
|
||||
new Color(168, 85, 247), new Color(239, 68, 68)
|
||||
};
|
||||
|
||||
for (int i = 0; i < actionTitles.length; i++) {
|
||||
JButton actionButton = createActionButton(actionTitles[i], actionIcons[i], actionColors[i]);
|
||||
final int index = i;
|
||||
actionButton.addActionListener(e -> handleAction(index));
|
||||
actionsGrid.add(actionButton);
|
||||
}
|
||||
|
||||
panel.add(titleLabel, BorderLayout.NORTH);
|
||||
panel.add(actionsGrid, BorderLayout.CENTER);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JButton createActionButton(String title, String icon, Color color) {
|
||||
JButton button = new JButton();
|
||||
button.setUI(new RoundedButtonUI(10));
|
||||
button.setLayout(new BorderLayout());
|
||||
button.setBackground(color);
|
||||
button.setForeground(Color.WHITE);
|
||||
@@ -294,94 +356,84 @@ public class DashboardPage extends JPanel {
|
||||
button.setFocusPainted(false);
|
||||
button.setBorder(BorderFactory.createEmptyBorder(15, 10, 15, 10));
|
||||
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
|
||||
JLabel iconLabel = new JLabel(icon);
|
||||
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
|
||||
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 28));
|
||||
iconLabel.setForeground(Color.WHITE);
|
||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
JLabel titleLabel = new JLabel(title);
|
||||
titleLabel.setForeground(Color.WHITE);
|
||||
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
button.add(iconLabel, BorderLayout.CENTER);
|
||||
button.add(titleLabel, BorderLayout.SOUTH);
|
||||
|
||||
// Hover effect
|
||||
button.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
button.setBackground(color.darker());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
button.setBackground(color);
|
||||
}
|
||||
public void mouseEntered(MouseEvent e) { button.setBackground(color.darker()); }
|
||||
public void mouseExited(MouseEvent e) { button.setBackground(color); }
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
private void loadDashboardData() {
|
||||
// Load metrics with sample data
|
||||
updateMetrics(12, 5, 3, 156);
|
||||
loadCurrentQueue();
|
||||
updateMetrics(3);
|
||||
updateDateTime();
|
||||
}
|
||||
|
||||
private void updateMetrics(int todayPatients, int waitingPatients, int recheckPatients, int totalPatients) {
|
||||
if (todayPatientsLabel != null) todayPatientsLabel.setText(String.valueOf(todayPatients));
|
||||
if (waitingPatientsLabel != null) waitingPatientsLabel.setText(String.valueOf(waitingPatients));
|
||||
|
||||
private void updateMetrics(int recheckPatients) {
|
||||
if (recheckPatientsLabel != null) recheckPatientsLabel.setText(String.valueOf(recheckPatients));
|
||||
if (totalPatientsLabel != null) totalPatientsLabel.setText(String.valueOf(totalPatients));
|
||||
}
|
||||
|
||||
private void loadCurrentQueue() {
|
||||
DefaultTableModel model = (DefaultTableModel) currentQueueTable.getModel();
|
||||
model.setRowCount(0); // Clear existing data
|
||||
|
||||
// Sample queue data
|
||||
String[][] queueData = {
|
||||
{"1", "BN-00123", "Nguyễn Văn An", "Khám tổng quát", "Đang chờ"},
|
||||
{"2", "BN-00124", "Trần Thị Bình", "Siêu âm", "Đang khám"},
|
||||
{"3", "BN-00125", "Lê Hoàng Cường", "Tái khám", "Đang chờ"},
|
||||
{"4", "BN-00126", "Phạm Thị Dung", "Khám thai", "Đang chờ"},
|
||||
{"5", "BN-00127", "Hoàng Văn Em", "Khám tổng quát", "Đang chờ"}
|
||||
};
|
||||
|
||||
for (String[] row : queueData) {
|
||||
model.addRow(row);
|
||||
private void updateDateTime() {
|
||||
if (dateTimeLabel != null) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String date = now.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
|
||||
String time = now.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
dateTimeLabel.setText("<html><div style='text-align: center;'>" + date + "<br>" + time + "</div></html>");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateQueueTable() {
|
||||
DefaultTableModel model = (DefaultTableModel) currentQueueTable.getModel();
|
||||
model.setRowCount(0);
|
||||
|
||||
if (patientQueue != null) {
|
||||
for (Patient patient : patientQueue) {
|
||||
Object[] rowData = {
|
||||
patient.getQueueNumber(),
|
||||
patient.getCheckupId(),
|
||||
patient.getCustomerLastName() + " " + patient.getCustomerFirstName(),
|
||||
DateUtils.extractYearFromTimestamp(patient.getCustomerDob()),
|
||||
patient.getCheckupType(),
|
||||
patient.getStatus()
|
||||
};
|
||||
model.addRow(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
long waitingCount = patientQueue.stream().filter(p -> "CHỜ KHÁM".equals(p.getStatus())).count();
|
||||
if (waitingPatientsLabel != null) {
|
||||
waitingPatientsLabel.setText(String.valueOf(waitingCount));
|
||||
}
|
||||
|
||||
setQueueTableColumnWidths();
|
||||
}
|
||||
|
||||
private void refreshDashboardData() {
|
||||
lastUpdateLabel.setText("Cập nhật lần cuối: " + getCurrentTime());
|
||||
loadDashboardData();
|
||||
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Đã làm mới dữ liệu thành công!",
|
||||
"Thông báo",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
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) {
|
||||
String[] messages = {
|
||||
"Chuyển đến trang thêm bệnh nhân...",
|
||||
"Gọi bệnh nhân tái khám...",
|
||||
"Mở báo cáo ngày...",
|
||||
"Kích hoạt chế độ khẩn cấp!"
|
||||
};
|
||||
|
||||
switch (actionIndex) {
|
||||
case 0: // Add Patient
|
||||
AddDialog addDialog = new AddDialog(mainFrame);
|
||||
addDialog.setVisible(true);
|
||||
break;
|
||||
case 1: // Call recheck patient
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Chức năng gọi bệnh nhân tái khám đang được phát triển",
|
||||
"Thông báo",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
RecheckUpDialog recheckUpDialog = new RecheckUpDialog(mainFrame);
|
||||
recheckUpDialog.setVisible(true);
|
||||
break;
|
||||
case 2: // Daily report
|
||||
JOptionPane.showMessageDialog(this,
|
||||
@@ -389,11 +441,8 @@ public class DashboardPage extends JPanel {
|
||||
"Thông báo",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
break;
|
||||
case 3: // Emergency
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Chế độ khẩn cấp được kích hoạt!",
|
||||
"KHẨN CẤP",
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
case 3:
|
||||
JOptionPane.showMessageDialog(this, "Chế độ khẩn cấp được kích hoạt!", "KHẨN CẤP", JOptionPane.WARNING_MESSAGE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
package BsK.client.ui.component.DashboardPage.RecheckUpDialog;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
// NEW: Imports for modern date/time handling
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import BsK.client.network.handler.ClientHandler;
|
||||
import BsK.common.util.network.NetworkUtil;
|
||||
import BsK.common.packet.req.GetRecheckUpListRequest;
|
||||
import BsK.common.packet.res.GetRecheckUpListResponse;
|
||||
import BsK.client.network.handler.ResponseListener;
|
||||
|
||||
public class RecheckUpDialog extends JDialog {
|
||||
|
||||
private JTable recheckTable;
|
||||
private DefaultTableModel tableModel;
|
||||
private JLabel qrCodeLabel;
|
||||
private JTextField sdtDisplayField;
|
||||
private JTextArea templateArea;
|
||||
private JTextArea contentInputArea;
|
||||
private JTextArea previewMessageArea;
|
||||
private JCheckBox includeTemplateCheckbox;
|
||||
private JTextArea deepLinkTextArea;
|
||||
|
||||
// NEW: Fields for filtering functionality
|
||||
private JComboBox<String> dateFilterComboBox;
|
||||
private String[][] allRecheckUpData; // Stores the original, unfiltered list from the server
|
||||
|
||||
private final ResponseListener<GetRecheckUpListResponse> getRecheckUpListResponseListener = this::handleGetRecheckUpListResponse;
|
||||
|
||||
public RecheckUpDialog(Frame parent) {
|
||||
super(parent, "Quản Lý Tái Khám", true);
|
||||
initializeDialog();
|
||||
|
||||
// Add the response listener
|
||||
ClientHandler.addResponseListener(GetRecheckUpListResponse.class, getRecheckUpListResponseListener);
|
||||
|
||||
// Send the initial request
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecheckUpListRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// Clean up listeners when the dialog is closed
|
||||
ClientHandler.deleteListener(GetRecheckUpListResponse.class, getRecheckUpListResponseListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// MODIFIED: This method now stores the master data list and calls the filter method
|
||||
private void handleGetRecheckUpListResponse(GetRecheckUpListResponse response) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
this.allRecheckUpData = response.getRecheckUpList();
|
||||
// Initially populate the table using the filter (which defaults to "All")
|
||||
filterAndDisplayTableData();
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeDialog() {
|
||||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||
setSize(1200, 800);
|
||||
setLocationRelativeTo(getParent());
|
||||
setLayout(new BorderLayout());
|
||||
getContentPane().setBackground(new Color(245, 247, 250));
|
||||
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createLeftPanel(), createRightPanel());
|
||||
splitPane.setDividerLocation(750);
|
||||
splitPane.setResizeWeight(0.65);
|
||||
splitPane.setBorder(null);
|
||||
|
||||
add(splitPane, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
// MODIFIED: The left panel now includes a filter panel at the top
|
||||
private JPanel createLeftPanel() {
|
||||
JPanel leftPanel = new JPanel(new BorderLayout(10, 10));
|
||||
leftPanel.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
leftPanel.setOpaque(false);
|
||||
|
||||
// NEW: Panel for the date filter ComboBox
|
||||
JPanel filterPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
filterPanel.setOpaque(false);
|
||||
filterPanel.setBorder(new EmptyBorder(0, 0, 10, 0)); // Bottom padding
|
||||
|
||||
JLabel filterLabel = new JLabel("Lọc theo ngày tái khám: ");
|
||||
filterLabel.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
|
||||
String[] filterOptions = {"Tất cả", "Hôm nay", "Ngày mai", "Trong 2 ngày tới", "Trong 3 ngày tới", "Trong 1 tuần tới"};
|
||||
dateFilterComboBox = new JComboBox<>(filterOptions);
|
||||
dateFilterComboBox.setFont(new Font("Segoe UI", Font.PLAIN, 14));
|
||||
dateFilterComboBox.addActionListener(e -> filterAndDisplayTableData());
|
||||
|
||||
filterPanel.add(filterLabel);
|
||||
filterPanel.add(dateFilterComboBox);
|
||||
|
||||
leftPanel.add(filterPanel, BorderLayout.NORTH); // Add filter to the top
|
||||
leftPanel.add(createTablePanel(), BorderLayout.CENTER);
|
||||
return leftPanel;
|
||||
}
|
||||
|
||||
private JPanel createTablePanel() {
|
||||
JPanel tablePanel = new JPanel(new BorderLayout());
|
||||
tablePanel.setOpaque(false);
|
||||
String[] columnNames = {"Họ và tên", "Số điện thoại", "Ngày tái khám", "Ngày nhắc"};
|
||||
tableModel = new DefaultTableModel(columnNames, 0) {
|
||||
public boolean isCellEditable(int row, int column) { return false; }
|
||||
};
|
||||
recheckTable = new JTable(tableModel);
|
||||
recheckTable.setRowHeight(30);
|
||||
recheckTable.setFont(new Font("Segoe UI", Font.PLAIN, 14));
|
||||
recheckTable.getTableHeader().setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
recheckTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
recheckTable.getTableHeader().setReorderingAllowed(false);
|
||||
|
||||
recheckTable.getSelectionModel().addListSelectionListener(e -> {
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
handleTableSelectionChange();
|
||||
}
|
||||
});
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(recheckTable);
|
||||
tablePanel.add(scrollPane, BorderLayout.CENTER);
|
||||
return tablePanel;
|
||||
}
|
||||
|
||||
// MODIFIED: Updated the "Làm mới" button's action listener to be a full refresh
|
||||
private JPanel createRightPanel() {
|
||||
JPanel rightPanel = new JPanel(new BorderLayout(10, 15));
|
||||
rightPanel.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
rightPanel.setOpaque(false);
|
||||
|
||||
JPanel qrPanel = new JPanel(new GridBagLayout());
|
||||
qrPanel.setBackground(Color.WHITE);
|
||||
qrPanel.setBorder(BorderFactory.createTitledBorder("Mã QR"));
|
||||
qrPanel.setPreferredSize(new Dimension(300, 300));
|
||||
qrCodeLabel = new JLabel("Chọn bệnh nhân để tạo tin nhắn", SwingConstants.CENTER);
|
||||
qrCodeLabel.setFont(new Font("Segoe UI", Font.ITALIC, 14));
|
||||
qrPanel.add(qrCodeLabel);
|
||||
rightPanel.add(qrPanel, BorderLayout.NORTH);
|
||||
|
||||
JPanel centerContentPanel = new JPanel(new GridBagLayout());
|
||||
centerContentPanel.setOpaque(false);
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.insets = new Insets(5, 0, 5, 0);
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc.weightx = 1.0;
|
||||
|
||||
gbc.gridy = 0;
|
||||
sdtDisplayField = new JTextField();
|
||||
sdtDisplayField.setEditable(false);
|
||||
sdtDisplayField.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
sdtDisplayField.setBackground(new Color(235, 235, 235));
|
||||
sdtDisplayField.setBorder(createTitledCompoundBorder("SĐT (từ bảng)"));
|
||||
centerContentPanel.add(sdtDisplayField, gbc);
|
||||
|
||||
gbc.gridy = 1; gbc.ipady = 0;
|
||||
includeTemplateCheckbox = new JCheckBox("Bao gồm mẫu tin nhắn", true);
|
||||
includeTemplateCheckbox.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
includeTemplateCheckbox.setOpaque(false);
|
||||
includeTemplateCheckbox.addActionListener(e -> updatePreview());
|
||||
centerContentPanel.add(includeTemplateCheckbox, gbc);
|
||||
|
||||
gbc.gridy = 2; gbc.ipady = 20;
|
||||
templateArea = new JTextArea();
|
||||
templateArea.setEditable(false);
|
||||
templateArea.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
templateArea.setBackground(new Color(235, 235, 235));
|
||||
templateArea.setLineWrap(true);
|
||||
templateArea.setWrapStyleWord(true);
|
||||
JScrollPane templateScrollPane = new JScrollPane(templateArea);
|
||||
templateScrollPane.setBorder(createTitledCompoundBorder("Mẫu tin nhắn (tự động)"));
|
||||
centerContentPanel.add(templateScrollPane, gbc);
|
||||
|
||||
gbc.gridy = 3; gbc.ipady = 40;
|
||||
contentInputArea = new JTextArea();
|
||||
contentInputArea.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
contentInputArea.setLineWrap(true);
|
||||
contentInputArea.setWrapStyleWord(true);
|
||||
contentInputArea.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override public void insertUpdate(DocumentEvent e) { updatePreview(); }
|
||||
@Override public void removeUpdate(DocumentEvent e) { updatePreview(); }
|
||||
@Override public void changedUpdate(DocumentEvent e) { updatePreview(); }
|
||||
});
|
||||
JScrollPane contentScrollPane = new JScrollPane(contentInputArea);
|
||||
contentScrollPane.setBorder(createTitledCompoundBorder("Nội dung tin nhắn (tùy chọn)"));
|
||||
centerContentPanel.add(contentScrollPane, gbc);
|
||||
|
||||
gbc.gridy = 4; gbc.ipady = 40;
|
||||
previewMessageArea = new JTextArea();
|
||||
previewMessageArea.setEditable(false);
|
||||
previewMessageArea.setFont(new Font("Segoe UI", Font.ITALIC, 12));
|
||||
previewMessageArea.setBackground(new Color(240, 240, 240));
|
||||
previewMessageArea.setLineWrap(true);
|
||||
previewMessageArea.setWrapStyleWord(true);
|
||||
JScrollPane previewScrollPane = new JScrollPane(previewMessageArea);
|
||||
previewScrollPane.setBorder(createTitledCompoundBorder("Xem trước tin nhắn (tổng hợp)"));
|
||||
centerContentPanel.add(previewScrollPane, gbc);
|
||||
|
||||
rightPanel.add(centerContentPanel, BorderLayout.CENTER);
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
||||
buttonPanel.setOpaque(false);
|
||||
JButton resetButton = new JButton("Làm mới");
|
||||
resetButton.setFont(new Font("Segoe UI", Font.BOLD, 12));
|
||||
resetButton.addActionListener(e -> {
|
||||
// This action now fully refreshes the view and data
|
||||
dateFilterComboBox.setSelectedIndex(0); // Reset filter to "All"
|
||||
recheckTable.clearSelection();
|
||||
clearRightPanel();
|
||||
// Request latest data from the server
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecheckUpListRequest());
|
||||
});
|
||||
JButton generateButton = new JButton("Tạo Tin & QR");
|
||||
generateButton.setFont(new Font("Segoe UI", Font.BOLD, 12));
|
||||
generateButton.addActionListener(e -> generateMessageAndQr());
|
||||
buttonPanel.add(resetButton);
|
||||
buttonPanel.add(generateButton);
|
||||
rightPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
return rightPanel;
|
||||
}
|
||||
|
||||
// NEW: This method filters the master data list and populates the table
|
||||
private void filterAndDisplayTableData() {
|
||||
tableModel.setRowCount(0); // Clear the visible table
|
||||
clearRightPanel(); // Clear the details panel on the right
|
||||
|
||||
if (allRecheckUpData == null) {
|
||||
return; // Exit if there's no data to filter
|
||||
}
|
||||
|
||||
String selectedFilter = (String) dateFilterComboBox.getSelectedItem();
|
||||
if (selectedFilter == null) {
|
||||
selectedFilter = "Tất cả"; // Default case
|
||||
}
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
|
||||
|
||||
for (String[] row : allRecheckUpData) {
|
||||
String dateString = row[2];
|
||||
try {
|
||||
LocalDate recheckDate = LocalDate.parse(dateString, formatter);
|
||||
boolean shouldAddRow = false;
|
||||
|
||||
switch (selectedFilter) {
|
||||
case "Tất cả":
|
||||
shouldAddRow = true;
|
||||
break;
|
||||
case "Hôm nay":
|
||||
shouldAddRow = recheckDate.isEqual(today);
|
||||
break;
|
||||
case "Ngày mai":
|
||||
shouldAddRow = recheckDate.isEqual(today.plusDays(1));
|
||||
break;
|
||||
case "Trong 2 ngày tới": // includes today and tomorrow
|
||||
shouldAddRow = !recheckDate.isBefore(today) && recheckDate.isBefore(today.plusDays(2));
|
||||
break;
|
||||
case "Trong 3 ngày tới":
|
||||
shouldAddRow = !recheckDate.isBefore(today) && recheckDate.isBefore(today.plusDays(3));
|
||||
break;
|
||||
case "Trong 1 tuần tới":
|
||||
shouldAddRow = !recheckDate.isBefore(today) && recheckDate.isBefore(today.plusDays(7));
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldAddRow) {
|
||||
tableModel.addRow(row);
|
||||
}
|
||||
|
||||
} catch (DateTimeParseException e) {
|
||||
System.err.println("Could not parse date string: " + dateString);
|
||||
// If the filter is "All", add rows with unparseable dates so they are not lost
|
||||
if ("Tất cả".equals(selectedFilter)) {
|
||||
tableModel.addRow(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTableSelectionChange() {
|
||||
clearRightPanel();
|
||||
int selectedRow = recheckTable.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
String sdt = (String) tableModel.getValueAt(selectedRow, 1);
|
||||
sdtDisplayField.setText(sdt);
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePreview() {
|
||||
int selectedRow = recheckTable.getSelectedRow();
|
||||
if (selectedRow == -1) {
|
||||
templateArea.setText("");
|
||||
previewMessageArea.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
String patientName = (String) tableModel.getValueAt(selectedRow, 0);
|
||||
String recheckDate = (String) tableModel.getValueAt(selectedRow, 2);
|
||||
String customContent = contentInputArea.getText();
|
||||
|
||||
String templateText = String.format("Phòng khám Bs.K xin thông báo: Anh/Chị %s có lịch tái khám vào ngày %s.", patientName, recheckDate);
|
||||
templateArea.setText(templateText);
|
||||
|
||||
String fullMessage;
|
||||
if (includeTemplateCheckbox.isSelected()) {
|
||||
fullMessage = templateText + " " + customContent;
|
||||
} else {
|
||||
fullMessage = customContent;
|
||||
}
|
||||
previewMessageArea.setText(fullMessage.trim());
|
||||
}
|
||||
|
||||
private void generateMessageAndQr() {
|
||||
int selectedRow = recheckTable.getSelectedRow();
|
||||
if (selectedRow == -1) {
|
||||
JOptionPane.showMessageDialog(this, "Vui lòng chọn một bệnh nhân.", "Chưa chọn bệnh nhân", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
String sdt = (String) tableModel.getValueAt(selectedRow, 1);
|
||||
String messageBody = previewMessageArea.getText();
|
||||
|
||||
if (messageBody.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(this, "Nội dung tin nhắn trống.", "Nội dung trống", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String formattedSdt = formatPhoneNumberForSms(sdt);
|
||||
String encodedBody = URLEncoder.encode(messageBody, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
|
||||
String smsDeepLink = "sms:" + formattedSdt + "?body=" + encodedBody;
|
||||
|
||||
BufferedImage qrImage = generateQrCodeImage(smsDeepLink);
|
||||
Image scaledQr = qrImage.getScaledInstance(280, 280, Image.SCALE_SMOOTH);
|
||||
qrCodeLabel.setIcon(new ImageIcon(scaledQr));
|
||||
qrCodeLabel.setText("");
|
||||
|
||||
} catch (WriterException e) {
|
||||
qrCodeLabel.setIcon(null);
|
||||
qrCodeLabel.setText("Lỗi tạo QR");
|
||||
JOptionPane.showMessageDialog(this, "Lỗi khi tạo mã QR.", "Lỗi", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatPhoneNumberForSms(String localNumber) {
|
||||
if (localNumber != null && !localNumber.trim().isEmpty()) {
|
||||
if (localNumber.startsWith("0")) {
|
||||
return "+84" + localNumber.substring(1);
|
||||
}
|
||||
}
|
||||
return localNumber;
|
||||
}
|
||||
|
||||
private BufferedImage generateQrCodeImage(String text) throws WriterException {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
hints.put(EncodeHintType.MARGIN, 1);
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
|
||||
|
||||
BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, 300, 300, hints);
|
||||
|
||||
int width = bitMatrix.getWidth();
|
||||
int height = bitMatrix.getHeight();
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
image.createGraphics();
|
||||
|
||||
Graphics2D graphics = (Graphics2D) image.getGraphics();
|
||||
graphics.setColor(Color.WHITE);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.setColor(Color.BLACK);
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
if (bitMatrix.get(i, j)) {
|
||||
graphics.fillRect(i, j, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private void clearRightPanel() {
|
||||
qrCodeLabel.setIcon(null);
|
||||
qrCodeLabel.setText("Chọn bệnh nhân để tạo tin nhắn");
|
||||
sdtDisplayField.setText("");
|
||||
includeTemplateCheckbox.setSelected(true);
|
||||
templateArea.setText("");
|
||||
contentInputArea.setText("");
|
||||
previewMessageArea.setText("");
|
||||
}
|
||||
|
||||
private CompoundBorder createTitledCompoundBorder(String title) {
|
||||
return new CompoundBorder(
|
||||
BorderFactory.createTitledBorder(null, title, 0, 0,
|
||||
new Font("Segoe UI", Font.PLAIN, 12), new Color(60, 60, 60)),
|
||||
new EmptyBorder(2, 5, 2, 5)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import java.util.Properties;
|
||||
@Slf4j
|
||||
public class AddDialog extends JDialog {
|
||||
private static final Logger log = LoggerFactory.getLogger(AddDialog.class);
|
||||
// --- Left Panel (Input Form) ---
|
||||
private JTextField patientNameField;
|
||||
private JTextField patientYearField;
|
||||
private JTextField patientIdField;
|
||||
@@ -40,24 +41,32 @@ public class AddDialog extends JDialog {
|
||||
private JComboBox patientGenderField;
|
||||
private JComboBox wardComboBox, provinceComboBox;
|
||||
private JTextField customerAddressField;
|
||||
private JDatePickerImpl dobPicker;
|
||||
|
||||
// --- Right Panel (Search & Table) ---
|
||||
private JTextField searchNameField; // Dedicated search field
|
||||
private JTextField searchPhoneField; // Dedicated search field
|
||||
private DefaultTableModel patientTableModel;
|
||||
private JTable patientTable;
|
||||
private String[] patientColumns = {"Mã BN", "Tên Bệnh Nhân", "Năm sinh", "Số điện thoại" ,"Địa chỉ"};
|
||||
private String[][] patientData;
|
||||
|
||||
// --- Checkup & Action Buttons ---
|
||||
private JComboBox<DoctorItem> doctorComboBox;
|
||||
private JComboBox<String> checkupTypeComboBox;
|
||||
JButton saveButton;
|
||||
private JButton addPatientButton;
|
||||
private JButton clearButton;
|
||||
|
||||
// --- State & Listeners ---
|
||||
private final ResponseListener<GetRecentPatientResponse> getRecentPatientResponseListener = this::getRecentPatientHandler;
|
||||
private final ResponseListener<GetWardResponse> wardResponseListener = this::handleGetWardResponse;
|
||||
private final ResponseListener<AddPatientResponse> addPatientResponseListener = this::handleAddPatientResponse;
|
||||
private final ResponseListener<AddCheckupResponse> addCheckupResponseListener = this::handleAddCheckupResponse;
|
||||
private JComboBox<DoctorItem> doctorComboBox;
|
||||
private JComboBox<String> checkupTypeComboBox;
|
||||
JButton saveButton;
|
||||
private DefaultComboBoxModel<String> wardModel;
|
||||
private JDatePickerImpl dobPicker;
|
||||
private JButton addPatientButton;
|
||||
private JButton clearButton;
|
||||
private String targetWard = null;
|
||||
|
||||
// Search and pagination controls (removed search buttons)
|
||||
// --- Pagination ---
|
||||
private JSpinner pageSpinner;
|
||||
private JLabel paginationLabel;
|
||||
private int currentPage = 1;
|
||||
@@ -65,7 +74,7 @@ public class AddDialog extends JDialog {
|
||||
private int totalCount = 0;
|
||||
private final int pageSize = 20;
|
||||
|
||||
// Debounce search timers
|
||||
// --- Debounce Search ---
|
||||
private Timer nameSearchTimer;
|
||||
private Timer phoneSearchTimer;
|
||||
|
||||
@@ -100,39 +109,48 @@ public class AddDialog extends JDialog {
|
||||
|
||||
private void sendPatientSearchRequest(String searchName, String searchPhone, int page) {
|
||||
log.info("Sending GetRecentPatientRequest with search: name='{}', phone='{}', page={}", searchName, searchPhone, page);
|
||||
ClientHandler.addResponseListener(GetRecentPatientResponse.class, getRecentPatientResponseListener);
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetRecentPatientRequest(searchName, searchPhone, page, pageSize));
|
||||
}
|
||||
|
||||
private void getRecentPatientHandler(GetRecentPatientResponse response) {
|
||||
log.info("Received GetRecentPatientResponse");
|
||||
patientData = response.getPatientData();
|
||||
|
||||
// Update pagination info
|
||||
currentPage = response.getCurrentPage();
|
||||
totalPages = response.getTotalPages();
|
||||
totalCount = response.getTotalCount();
|
||||
|
||||
// Update table data
|
||||
patientTableModel.setDataVector(patientData, patientColumns);
|
||||
// Set column widths: id, name, birthyear, sdt, address
|
||||
int[] columnWidths = {60, 180, 70, 120, 300};
|
||||
for (int i = 0; i < columnWidths.length; i++) {
|
||||
patientTable.getColumnModel().getColumn(i).setPreferredWidth(columnWidths[i]);
|
||||
}
|
||||
|
||||
// Update pagination controls
|
||||
if (pageSpinner != null) {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel) pageSpinner.getModel();
|
||||
model.setMaximum(Math.max(1, totalPages));
|
||||
model.setValue(currentPage);
|
||||
paginationLabel.setText("/ " + totalPages + " (" + totalCount + " bệnh nhân)");
|
||||
}
|
||||
|
||||
// Clear selection and update button states
|
||||
patientTable.clearSelection();
|
||||
saveButton.setEnabled(false);
|
||||
addPatientButton.setEnabled(true);
|
||||
|
||||
// All UI updates MUST be executed on the Event Dispatch Thread (EDT).
|
||||
// This wrapper ensures the code runs on the correct thread, preventing corruption.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
patientData = response.getPatientData();
|
||||
|
||||
// Update pagination info
|
||||
currentPage = response.getCurrentPage();
|
||||
totalPages = response.getTotalPages();
|
||||
totalCount = response.getTotalCount();
|
||||
|
||||
// Update table data safely on the EDT
|
||||
patientTableModel.setDataVector(patientData, patientColumns);
|
||||
|
||||
// Set column widths after the model has been safely updated.
|
||||
// This loop is also made safer by checking the actual number of columns.
|
||||
int[] columnWidths = {60, 180, 70, 120, 300};
|
||||
int columnCountInTable = patientTable.getColumnModel().getColumnCount();
|
||||
int columnsToConfigure = Math.min(columnCountInTable, columnWidths.length);
|
||||
|
||||
for (int i = 0; i < columnsToConfigure; i++) {
|
||||
patientTable.getColumnModel().getColumn(i).setPreferredWidth(columnWidths[i]);
|
||||
}
|
||||
|
||||
// Update pagination controls
|
||||
if (pageSpinner != null) {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel) pageSpinner.getModel();
|
||||
model.setMaximum(Math.max(1, totalPages));
|
||||
model.setValue(currentPage);
|
||||
paginationLabel.setText("/ " + totalPages + " (" + totalCount + " bệnh nhân)");
|
||||
}
|
||||
|
||||
// Clear selection and update button states
|
||||
patientTable.clearSelection();
|
||||
saveButton.setEnabled(false);
|
||||
addPatientButton.setEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
private int findProvinceIndex(String province) {
|
||||
@@ -162,7 +180,7 @@ public class AddDialog extends JDialog {
|
||||
ClientHandler.deleteListener(AddPatientResponse.class, addPatientResponseListener);
|
||||
ClientHandler.deleteListener(GetRecentPatientResponse.class, getRecentPatientResponseListener);
|
||||
ClientHandler.deleteListener(GetWardResponse.class, wardResponseListener);
|
||||
|
||||
|
||||
// Clean up timers
|
||||
if (nameSearchTimer != null && nameSearchTimer.isRunning()) {
|
||||
nameSearchTimer.stop();
|
||||
@@ -215,11 +233,12 @@ public class AddDialog extends JDialog {
|
||||
});
|
||||
|
||||
// Send request to get the latest 20 patients in the database
|
||||
sendGetRecentPatientRequest();
|
||||
ClientHandler.addResponseListener(GetWardResponse.class, wardResponseListener);
|
||||
ClientHandler.addResponseListener(AddPatientResponse.class, addPatientResponseListener);
|
||||
ClientHandler.addResponseListener(AddCheckupResponse.class, addCheckupResponseListener);
|
||||
ClientHandler.addResponseListener(GetRecentPatientResponse.class, getRecentPatientResponseListener);
|
||||
|
||||
sendGetRecentPatientRequest();
|
||||
// Add patent table on the right side
|
||||
// Add a scroll pane to the table
|
||||
patientTableModel = new DefaultTableModel(patientData, patientColumns) {
|
||||
@@ -518,10 +537,6 @@ public class AddDialog extends JDialog {
|
||||
gbc.weightx = 0.0; // Reset weightx for any subsequent components in this panel
|
||||
gbc.gridwidth = 1; // Reset gridwidth
|
||||
|
||||
|
||||
// Setup debounce search functionality
|
||||
setupDebounceSearch();
|
||||
|
||||
// Province ComboBox Listener
|
||||
provinceComboBox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
@@ -538,9 +553,6 @@ public class AddDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Ward ComboBox Listener
|
||||
|
||||
inputPanel.setMinimumSize(new Dimension(400, 0));
|
||||
|
||||
// Wrap inputPanel in a JScrollPane
|
||||
@@ -551,6 +563,7 @@ public class AddDialog extends JDialog {
|
||||
|
||||
// Create right panel for table and pagination
|
||||
JPanel rightPanel = new JPanel(new BorderLayout());
|
||||
rightPanel.add(createSearchPanel(), BorderLayout.NORTH); // Add new search panel
|
||||
rightPanel.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Create pagination panel
|
||||
@@ -598,7 +611,7 @@ public class AddDialog extends JDialog {
|
||||
}
|
||||
int doctorId = Integer.parseInt(selectedDoctor.getId());
|
||||
String selectedCheckupType = (String) checkupTypeComboBox.getSelectedItem();
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new AddCheckupRequest(patientId, doctorId, LocalStorage.userId, selectedCheckupType, "ĐANG KHÁM"));
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new AddCheckupRequest(patientId, doctorId, LocalStorage.userId, selectedCheckupType, "CHỜ KHÁM"));
|
||||
|
||||
});
|
||||
saveButton.setEnabled(false);
|
||||
@@ -684,24 +697,18 @@ public class AddDialog extends JDialog {
|
||||
JOptionPane.showMessageDialog(null, "Định dạng ngày không hợp lệ: " + dateText);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setupDebounceSearch();
|
||||
|
||||
clearButton.addActionListener(e -> {
|
||||
// Stop any ongoing search timers
|
||||
if (nameSearchTimer != null && nameSearchTimer.isRunning()) {
|
||||
nameSearchTimer.stop();
|
||||
}
|
||||
if (phoneSearchTimer != null && phoneSearchTimer.isRunning()) {
|
||||
phoneSearchTimer.stop();
|
||||
}
|
||||
|
||||
// Clear all fields
|
||||
// Clear all fields on the left
|
||||
patientNameField.setText("");
|
||||
patientPhoneField.setText("");
|
||||
patientIdField.setText("");
|
||||
cccdField.setText("");
|
||||
customerAddressField.setText("");
|
||||
|
||||
// Reset combo boxes
|
||||
// Reset combo boxes on the left
|
||||
patientGenderField.setSelectedIndex(0);
|
||||
provinceComboBox.setSelectedIndex(0);
|
||||
wardComboBox.setSelectedIndex(0);
|
||||
@@ -711,13 +718,14 @@ public class AddDialog extends JDialog {
|
||||
}
|
||||
checkupTypeComboBox.setSelectedIndex(0);
|
||||
|
||||
// Reset targetWard
|
||||
targetWard = null;
|
||||
|
||||
// Clear date picker
|
||||
dobPicker.getModel().setValue(null);
|
||||
dobPicker.getJFormattedTextField().setText("");
|
||||
|
||||
// Clear right-side search fields
|
||||
searchNameField.setText("");
|
||||
searchPhoneField.setText("");
|
||||
|
||||
// Clear table selection
|
||||
patientTable.clearSelection();
|
||||
|
||||
@@ -729,15 +737,13 @@ public class AddDialog extends JDialog {
|
||||
sendGetRecentPatientRequest();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Add pagination spinner listener
|
||||
pageSpinner.addChangeListener(e -> {
|
||||
int selectedPage = (Integer) pageSpinner.getValue();
|
||||
if (selectedPage != currentPage && selectedPage >= 1 && selectedPage <= totalPages) {
|
||||
// Determine current search parameters
|
||||
String searchName = patientNameField.getText().trim();
|
||||
String searchPhone = patientPhoneField.getText().trim();
|
||||
// Determine current search parameters from the dedicated search fields
|
||||
String searchName = searchNameField.getText().trim();
|
||||
String searchPhone = searchPhoneField.getText().trim();
|
||||
searchName = searchName.isEmpty() ? null : searchName;
|
||||
searchPhone = searchPhone.isEmpty() ? null : searchPhone;
|
||||
|
||||
@@ -745,29 +751,25 @@ public class AddDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void setupDebounceSearch() {
|
||||
// Initialize timers for debounce search (1 second delay)
|
||||
nameSearchTimer = new Timer(1000, e -> {
|
||||
String searchName = patientNameField.getText().trim();
|
||||
String searchPhone = patientPhoneField.getText().trim();
|
||||
searchName = searchName.isEmpty() ? null : searchName;
|
||||
searchPhone = searchPhone.isEmpty() ? null : searchPhone;
|
||||
sendPatientSearchRequest(searchName, searchPhone, 1);
|
||||
String searchName = searchNameField.getText().trim();
|
||||
String searchPhone = searchPhoneField.getText().trim();
|
||||
sendPatientSearchRequest(searchName.isEmpty() ? null : searchName, searchPhone.isEmpty() ? null : searchPhone, 1);
|
||||
});
|
||||
nameSearchTimer.setRepeats(false);
|
||||
|
||||
phoneSearchTimer = new Timer(1000, e -> {
|
||||
String searchName = patientNameField.getText().trim();
|
||||
String searchPhone = patientPhoneField.getText().trim();
|
||||
searchName = searchName.isEmpty() ? null : searchName;
|
||||
searchPhone = searchPhone.isEmpty() ? null : searchPhone;
|
||||
sendPatientSearchRequest(searchName, searchPhone, 1);
|
||||
String searchName = searchNameField.getText().trim();
|
||||
String searchPhone = searchPhoneField.getText().trim();
|
||||
sendPatientSearchRequest(searchName.isEmpty() ? null : searchName, searchPhone.isEmpty() ? null : searchPhone, 1);
|
||||
});
|
||||
phoneSearchTimer.setRepeats(false);
|
||||
|
||||
// Add DocumentListener to patientNameField for debounced search
|
||||
patientNameField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
// Add DocumentListener to searchNameField for debounced search
|
||||
searchNameField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
resetAndStartTimer(nameSearchTimer);
|
||||
@@ -784,8 +786,8 @@ public class AddDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
// Add DocumentListener to patientPhoneField for debounced search
|
||||
patientPhoneField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
// Add DocumentListener to searchPhoneField for debounced search
|
||||
searchPhoneField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
resetAndStartTimer(phoneSearchTimer);
|
||||
@@ -810,6 +812,29 @@ public class AddDialog extends JDialog {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private JPanel createSearchPanel() {
|
||||
JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchPanel.setBorder(BorderFactory.createTitledBorder("Tìm kiếm bệnh nhân"));
|
||||
|
||||
searchPanel.add(new JLabel("Tên BN:"));
|
||||
searchNameField = new JTextField(15);
|
||||
searchPanel.add(searchNameField);
|
||||
|
||||
searchPanel.add(new JLabel("SĐT:"));
|
||||
searchPhoneField = new JTextField(10);
|
||||
searchPanel.add(searchPhoneField);
|
||||
|
||||
JButton clearSearchButton = new JButton("Xóa tìm kiếm");
|
||||
clearSearchButton.addActionListener(e -> {
|
||||
searchNameField.setText("");
|
||||
searchPhoneField.setText("");
|
||||
sendGetRecentPatientRequest(); // Fetch initial list
|
||||
});
|
||||
searchPanel.add(clearSearchButton);
|
||||
|
||||
return searchPanel;
|
||||
}
|
||||
|
||||
private void handleRowSelection(int selectedRow) {
|
||||
if (selectedRow < 0) {
|
||||
return;
|
||||
@@ -890,15 +915,27 @@ public class AddDialog extends JDialog {
|
||||
|
||||
private void handleAddPatientResponse(AddPatientResponse response) {
|
||||
log.info("Received AddPatientResponse");
|
||||
if (response.isSuccess()) {
|
||||
JOptionPane.showMessageDialog(this, "Thêm bệnh nhân thành công");
|
||||
patientIdField.setText(String.valueOf(response.getPatientId()));
|
||||
addPatientButton.setEnabled(false); // Disable add patient button since a patient is now selected
|
||||
saveButton.setEnabled(true); // Enable save button since we now have a patient
|
||||
sendGetRecentPatientRequest();
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(this, response.getMessage());
|
||||
}
|
||||
|
||||
// This entire block of logic will now be executed on the Swing EDT
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (response.isSuccess()) {
|
||||
JOptionPane.showMessageDialog(this, "Thêm bệnh nhân thành công");
|
||||
|
||||
// Now that the JOptionPane is closed, the connection is still alive.
|
||||
// This packet will be sent successfully.
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetCheckUpQueueUpdateRequest());
|
||||
|
||||
patientIdField.setText(String.valueOf(response.getPatientId()));
|
||||
addPatientButton.setEnabled(false); // Disable add patient button
|
||||
saveButton.setEnabled(true); // Enable save button
|
||||
|
||||
// Refresh the patient list in the dialog
|
||||
sendGetRecentPatientRequest();
|
||||
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(this, response.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@ public class NavBar extends JPanel {
|
||||
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);
|
||||
@@ -98,8 +98,8 @@ public class NavBar extends JPanel {
|
||||
RoundedPanel itemPanel = new RoundedPanel(15, itemColor.brighter(), true);
|
||||
itemPanel.setLayout(new BorderLayout(2, 2));
|
||||
itemPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createEmptyBorder(2, 2, 2, 2),
|
||||
BorderFactory.createEmptyBorder(5, 10, 5, 10)
|
||||
BorderFactory.createEmptyBorder(2, 2, 2, 2),
|
||||
BorderFactory.createEmptyBorder(5, 10, 5, 10)
|
||||
));
|
||||
itemPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
itemPanel.setPreferredSize(new Dimension(100, 55));
|
||||
@@ -117,7 +117,6 @@ public class NavBar extends JPanel {
|
||||
ImageIcon scaledIcon = new ImageIcon(scaledImage);
|
||||
label.setIcon(scaledIcon);
|
||||
} catch (Exception e) {
|
||||
// log is not available here, print to stderr
|
||||
System.err.println("Error loading icon: " + iconFileName + " for nav item: " + itemText);
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -142,40 +141,38 @@ public class NavBar extends JPanel {
|
||||
}
|
||||
|
||||
if ("SettingsPage".equals(dest)) {
|
||||
// Open settings dialog instead of navigating to a page
|
||||
SettingsDialog settingsDialog = new SettingsDialog(mainFrame);
|
||||
settingsDialog.setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("Dữ liệu".equals(itemText)) {
|
||||
// If not currently on CheckUpPage, navigate there first
|
||||
if (!(mainFrame.getCurrentPage() instanceof CheckUpPage)) {
|
||||
mainFrame.showPage("CheckUpPage");
|
||||
// Update active nav item to CheckUpPage
|
||||
if (activeNavItem != null) {
|
||||
JPanel prevPanel = (JPanel) activeNavItem.getParent();
|
||||
prevPanel.setBackground(navItemColors[findNavItemIndex(activeNavItem.getText(), navBarItems)].brighter());
|
||||
activeNavItem.setForeground(defaultTextColor);
|
||||
}
|
||||
// Find and set the "Thăm khám" nav item as active
|
||||
for (int j = 0; j < navBarItems.length; j++) {
|
||||
if ("Thăm khám".equals(navBarItems[j])) {
|
||||
Component[] components = navItemsPanel.getComponents();
|
||||
if (j < components.length && components[j] instanceof JPanel) {
|
||||
JPanel checkupPanel = (JPanel) components[j];
|
||||
if (checkupPanel.getComponentCount() > 0 && checkupPanel.getComponent(0) instanceof JLabel) {
|
||||
JLabel checkupLabel = (JLabel) checkupPanel.getComponent(0);
|
||||
activeNavItem = checkupLabel;
|
||||
checkupPanel.setBackground(navItemColors[j]);
|
||||
checkupLabel.setForeground(activeTextColor);
|
||||
// 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);
|
||||
if ("Thăm khám".equals(potentialLabel.getText())) {
|
||||
activeNavItem = potentialLabel;
|
||||
((JPanel) comp).setBackground(navItemColors[j]);
|
||||
potentialLabel.setForeground(activeTextColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Open data dialog
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
DataDialog dataDialog = new DataDialog(mainFrame);
|
||||
dataDialog.setVisible(true);
|
||||
@@ -197,20 +194,28 @@ public class NavBar extends JPanel {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
if (label != activeNavItem) {
|
||||
itemPanel.setBackground(itemColor); // Darken background on hover
|
||||
label.setForeground(activeTextColor); // Set text to active color
|
||||
itemPanel.setBackground(itemColor);
|
||||
label.setForeground(activeTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
if (label != activeNavItem) {
|
||||
itemPanel.setBackground(itemColor.brighter()); // Revert to default inactive background
|
||||
label.setForeground(defaultTextColor); // Revert text color
|
||||
itemPanel.setBackground(itemColor.brighter());
|
||||
label.setForeground(defaultTextColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
navItemsPanel.add(itemPanel);
|
||||
|
||||
// ADDED: Add a visual separator after the second item ("Thăm khám")
|
||||
if (i == 1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
this.add(navItemsPanel);
|
||||
@@ -263,4 +268,4 @@ public class NavBar extends JPanel {
|
||||
item.addActionListener(action);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package BsK.common.packet.req;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
|
||||
public class GetRecheckUpListRequest implements Packet {
|
||||
public GetRecheckUpListRequest() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package BsK.common.packet.res;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GetRecheckUpListResponse implements Packet {
|
||||
private String[][] recheckUpList;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package BsK.common.packet.res;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class RecheckCountResponse implements Packet {
|
||||
private int recheckCount;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package BsK.common.packet.res;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class TodayPatientCountResponse implements Packet {
|
||||
private int totalPatientsToday;
|
||||
}
|
||||
@@ -37,6 +37,8 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.time.ZoneId;
|
||||
import java.time.LocalDate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -116,7 +118,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
"from checkup as a\n" +
|
||||
"join customer as c on a.customer_id = c.customer_id\n" +
|
||||
"join Doctor D on a.doctor_id = D.doctor_id\n" +
|
||||
"where a.status = 'ĐANG KHÁM'"
|
||||
"where a.status = 'ĐANG KHÁM' or a.status = 'CHỜ KHÁM'"
|
||||
);
|
||||
|
||||
if (!rs.isBeforeFirst()) {
|
||||
@@ -185,91 +187,9 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
|
||||
if (packet instanceof GetCheckUpQueueUpdateRequest) {
|
||||
log.debug("Received GetCheckUpQueueUpdateRequest");
|
||||
try {
|
||||
ResultSet rs = statement.executeQuery(
|
||||
"select a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name,\n" +
|
||||
"d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, a.status, a.customer_id, \n" +
|
||||
"c.customer_number, c.customer_address, a.customer_weight, a.customer_height, c.customer_gender, c.customer_dob, \n" +
|
||||
"a.checkup_type, a.conclusion, a.reCheckupDate, c.cccd_ddcn, a.heart_beat, a.blood_pressure, c.drive_url, a.doctor_ultrasound_id, a.queue_number\n" +
|
||||
"from checkup as a\n" +
|
||||
"join customer as c on a.customer_id = c.customer_id\n" +
|
||||
"join Doctor D on a.doctor_id = D.doctor_id\n" +
|
||||
"where a.status = 'ĐANG KHÁM'"
|
||||
);
|
||||
|
||||
if (!rs.isBeforeFirst()) {
|
||||
System.out.println("No data found in the checkup table.");
|
||||
// Also send an empty response to clients so they clear their queues
|
||||
int maxCurId = SessionManager.getMaxSessionId();
|
||||
for (int sessionId = 1; sessionId <= maxCurId; sessionId++) {
|
||||
UserUtil.sendPacket(sessionId, new GetCheckUpQueueUpdateResponse(new String[0][0]));
|
||||
}
|
||||
} else {
|
||||
ArrayList<String> resultList = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
String checkupId = rs.getString("checkup_id");
|
||||
String checkupDate = rs.getString("checkup_date");
|
||||
long checkupDateLong = Long.parseLong(checkupDate);
|
||||
Timestamp timestamp = new Timestamp(checkupDateLong);
|
||||
Date date = new Date(timestamp.getTime()); // Needed to recode
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
|
||||
checkupDate = sdf.format(date);
|
||||
String customerLastName = rs.getString("customer_last_name");
|
||||
String customerFirstName = rs.getString("customer_first_name");
|
||||
String doctorFirstName = rs.getString("doctor_first_name");
|
||||
String doctorLastName = rs.getString("doctor_last_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 = rs.getString("customer_weight");
|
||||
String customerHeight = rs.getString("customer_height");
|
||||
String customerGender = rs.getString("customer_gender");
|
||||
String customerDob = rs.getString("customer_dob");
|
||||
String checkupType = rs.getString("checkup_type");
|
||||
String conclusion = rs.getString("conclusion");
|
||||
String reCheckupDate = rs.getString("reCheckupDate");
|
||||
String cccdDdcn = rs.getString("cccd_ddcn");
|
||||
String heartBeat = rs.getString("heart_beat");
|
||||
String bloodPressure = rs.getString("blood_pressure");
|
||||
String driveUrl = rs.getString("drive_url");
|
||||
String doctorUltrasoundId = rs.getString("doctor_ultrasound_id");
|
||||
String queueNumber = String.format("%02d", rs.getInt("queue_number"));
|
||||
if (driveUrl == null) {
|
||||
driveUrl = "";
|
||||
}
|
||||
String result = String.join("|", checkupId,
|
||||
checkupDate, customerLastName, customerFirstName,
|
||||
doctorLastName + " " + doctorFirstName, suggestion,
|
||||
diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight,
|
||||
customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn, heartBeat, bloodPressure,
|
||||
driveUrl, doctorUltrasoundId, queueNumber
|
||||
);
|
||||
|
||||
resultList.add(result);
|
||||
|
||||
}
|
||||
|
||||
String[] resultString = resultList.toArray(new String[0]);
|
||||
String[][] resultArray = new String[resultString.length][];
|
||||
for (int i = 0; i < resultString.length; i++) {
|
||||
resultArray[i] = resultString[i].split("\\|");
|
||||
}
|
||||
//send to all
|
||||
int maxCurId = SessionManager.getMaxSessionId();
|
||||
for (int sessionId = 1; sessionId <= maxCurId; sessionId++) {
|
||||
UserUtil.sendPacket(sessionId, new GetCheckUpQueueUpdateResponse(resultArray));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
broadcastQueueUpdate();
|
||||
}
|
||||
|
||||
|
||||
// Get general doctor info
|
||||
if (packet instanceof GetDoctorGeneralInfo) {
|
||||
log.debug("Received GetDoctorGeneralInfo");
|
||||
@@ -627,36 +547,30 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
}
|
||||
|
||||
if (packet instanceof GetWardRequest getWardRequest) {
|
||||
log.debug("Received GetWardRequest");
|
||||
try{
|
||||
log.debug("Received GetWardRequest for province {}", getWardRequest.getProvinceId());
|
||||
String sql = "SELECT wards.name FROM wards WHERE province_code = ? ORDER BY wards.name";
|
||||
|
||||
PreparedStatement preparedStatement = Server.connection.prepareStatement(
|
||||
"SELECT wards.code, wards.name " +
|
||||
" FROM wards " +
|
||||
" WHERE province_code = ? " +
|
||||
" ORDER BY wards.name"
|
||||
);
|
||||
preparedStatement.setString(1, getWardRequest.getProvinceId());
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
|
||||
if (!rs.isBeforeFirst()) {
|
||||
System.out.println("No data found in the ward table.");
|
||||
} else {
|
||||
ArrayList<String> resultList = new ArrayList<>();
|
||||
resultList.add("Phường/Xã");
|
||||
while (rs.next()) {
|
||||
String wardName = rs.getString("name");
|
||||
resultList.add(wardName);
|
||||
// Use try-with-resources to automatically close resources
|
||||
try (PreparedStatement preparedStatement = Server.connection.prepareStatement(sql)) {
|
||||
preparedStatement.setString(1, getWardRequest.getProvinceId());
|
||||
try (ResultSet rs = preparedStatement.executeQuery()) {
|
||||
if (!rs.isBeforeFirst()) {
|
||||
System.out.println("No data found in the ward table.");
|
||||
// Send empty response
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(new String[]{"Phường/Xã"}));
|
||||
} else {
|
||||
ArrayList<String> resultList = new ArrayList<>();
|
||||
resultList.add("Phường/Xã");
|
||||
while (rs.next()) {
|
||||
resultList.add(rs.getString("name"));
|
||||
}
|
||||
String[] resultString = resultList.toArray(new String[0]);
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(resultString));
|
||||
}
|
||||
|
||||
String[] resultString = resultList.toArray(new String[0]);
|
||||
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(resultString));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SQLException e) {
|
||||
log.error("Error fetching wards for province {}", getWardRequest.getProvinceId(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
if (packet instanceof AddPatientRequest addPatientRequest) {
|
||||
@@ -754,8 +668,6 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
log.error("Error updating queue counter", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// To make it looks better when small number we mod it to 100 so the queue is only 1 to 100
|
||||
queueNumber = (queueNumber % 100) + 1;
|
||||
// First query: Insert Checkup
|
||||
PreparedStatement checkupStmt = Server.connection.prepareStatement(
|
||||
"INSERT INTO Checkup (customer_id, doctor_id, checkup_type, status, queue_number) VALUES (?, ?, ?, ?, ?)");
|
||||
@@ -798,6 +710,8 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
// Send immediate response to client
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(true, "Thêm bệnh nhân thành công", queueNumber));
|
||||
|
||||
broadcastQueueUpdate();
|
||||
|
||||
// Create Google Drive folder asynchronously (don't block the response)
|
||||
createCheckupGoogleDriveFolderAsync(generatedCheckupId, addCheckupRequest.getCustomerId());
|
||||
}
|
||||
@@ -1486,6 +1400,45 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
log.info("Sent sync response with {} images for checkupId: {}", imageDatas.size(), checkupId);
|
||||
}
|
||||
|
||||
if (packet instanceof GetRecheckUpListRequest getRecheckUpListRequest) {
|
||||
log.debug("Received GetRecheckUpListRequest");
|
||||
try {
|
||||
String sql = "SELECT c.customer_last_name, c.customer_first_name, c.customer_number, ch.reCheckupDate, ch.remind_date, ch.checkup_id " +
|
||||
"FROM Checkup ch " +
|
||||
"JOIN Customer c ON ch.customer_id = c.customer_id " +
|
||||
"WHERE ch.reCheckupDate BETWEEN ? AND ?";
|
||||
|
||||
long fromDate = System.currentTimeMillis();
|
||||
long toDate = fromDate + (7L * 24 * 60 * 60 * 1000);
|
||||
|
||||
PreparedStatement stmt = Server.connection.prepareStatement(sql);
|
||||
stmt.setLong(1, fromDate);
|
||||
stmt.setLong(2, toDate);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
List<String[]> results = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
String[] row = new String[5];
|
||||
row[0] = rs.getString("customer_last_name") + " " + rs.getString("customer_first_name");
|
||||
row[1] = rs.getString("customer_number");
|
||||
|
||||
long recheckTimestamp = rs.getLong("reCheckupDate");
|
||||
long remindTimestamp = rs.getLong("remind_date");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
|
||||
row[2] = sdf.format(new Date(recheckTimestamp));
|
||||
row[3] = sdf.format(new Date(remindTimestamp));
|
||||
row[4] = rs.getString("checkup_id");
|
||||
results.add(row);
|
||||
}
|
||||
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new GetRecheckUpListResponse(results.toArray(new String[0][])));
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.error("Error fetching recheck-up list", e);
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1889,4 +1842,120 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void broadcastQueueUpdate() {
|
||||
try {
|
||||
// --- EXISTING LOGIC FOR THE QUEUE (UNCHANGED) ---
|
||||
ResultSet rs = statement.executeQuery(
|
||||
"select a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name,\n" +
|
||||
"d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, a.status, a.customer_id, \n" +
|
||||
"c.customer_number, c.customer_address, a.customer_weight, a.customer_height, c.customer_gender, c.customer_dob, \n" +
|
||||
"a.checkup_type, a.conclusion, a.reCheckupDate, c.cccd_ddcn, a.heart_beat, a.blood_pressure, c.drive_url, a.doctor_ultrasound_id, a.queue_number\n" +
|
||||
"from checkup as a\n" +
|
||||
"join customer as c on a.customer_id = c.customer_id\n" +
|
||||
"join Doctor D on a.doctor_id = D.doctor_id\n" +
|
||||
"where a.status = 'ĐANG KHÁM' or a.status = 'CHỜ KHÁM'"
|
||||
);
|
||||
|
||||
String[][] resultArray;
|
||||
if (!rs.isBeforeFirst()) {
|
||||
System.out.println("No data found in the checkup table.");
|
||||
resultArray = new String[0][0];
|
||||
} else {
|
||||
// (Your existing code to process the queue results remains here)
|
||||
ArrayList<String> resultList = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
String checkupId = rs.getString("checkup_id");
|
||||
String checkupDate = rs.getString("checkup_date");
|
||||
long checkupDateLong = Long.parseLong(checkupDate);
|
||||
Timestamp timestamp = new Timestamp(checkupDateLong);
|
||||
Date date = new Date(timestamp.getTime());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
|
||||
checkupDate = sdf.format(date);
|
||||
String customerLastName = rs.getString("customer_last_name");
|
||||
String customerFirstName = rs.getString("customer_first_name");
|
||||
String doctorFirstName = rs.getString("doctor_first_name");
|
||||
String doctorLastName = rs.getString("doctor_last_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 = rs.getString("customer_weight");
|
||||
String customerHeight = rs.getString("customer_height");
|
||||
String customerGender = rs.getString("customer_gender");
|
||||
String customerDob = rs.getString("customer_dob");
|
||||
String checkupType = rs.getString("checkup_type");
|
||||
String conclusion = rs.getString("conclusion");
|
||||
String reCheckupDate = rs.getString("reCheckupDate");
|
||||
String cccdDdcn = rs.getString("cccd_ddcn");
|
||||
String heartBeat = rs.getString("heart_beat");
|
||||
String bloodPressure = rs.getString("blood_pressure");
|
||||
String driveUrl = rs.getString("drive_url");
|
||||
String doctorUltrasoundId = rs.getString("doctor_ultrasound_id");
|
||||
String queueNumber = String.format("%02d", rs.getInt("queue_number"));
|
||||
if (driveUrl == null) { driveUrl = ""; }
|
||||
String result = String.join("|", checkupId, checkupDate, customerLastName, customerFirstName,
|
||||
doctorLastName + " " + doctorFirstName, suggestion, diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight,
|
||||
customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn, heartBeat, bloodPressure,
|
||||
driveUrl, doctorUltrasoundId, queueNumber
|
||||
);
|
||||
resultList.add(result);
|
||||
}
|
||||
String[] resultString = resultList.toArray(new String[0]);
|
||||
resultArray = new String[resultString.length][];
|
||||
for (int i = 0; i < resultString.length; i++) {
|
||||
resultArray[i] = resultString[i].split("\\|");
|
||||
}
|
||||
}
|
||||
|
||||
// --- EXISTING LOGIC FOR TODAY'S PATIENT COUNT ---
|
||||
ZoneId zoneId = ZoneId.of("Asia/Ho_Chi_Minh");
|
||||
LocalDate today = LocalDate.now(zoneId);
|
||||
long startOfTodayMillis = today.atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
long endOfTodayMillis = today.plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1;
|
||||
String countQuery = String.format(
|
||||
"SELECT COUNT(DISTINCT a.customer_id) as patient_count FROM checkup as a WHERE a.checkup_date >= %d AND a.checkup_date <= %d",
|
||||
startOfTodayMillis, endOfTodayMillis
|
||||
);
|
||||
int totalPatientsToday = 0;
|
||||
ResultSet countRs = statement.executeQuery(countQuery);
|
||||
if (countRs.next()) {
|
||||
totalPatientsToday = countRs.getInt("patient_count");
|
||||
}
|
||||
|
||||
// --- ADDED LOGIC: QUERY FOR TODAY'S RE-CHECKUP COUNT ---
|
||||
String recheckQuery = String.format(
|
||||
"SELECT COUNT(*) as recheck_count FROM checkup WHERE reCheckupDate >= %d AND reCheckupDate <= %d",
|
||||
startOfTodayMillis, endOfTodayMillis
|
||||
);
|
||||
int totalRecheckToday = 0;
|
||||
ResultSet recheckRs = statement.executeQuery(recheckQuery);
|
||||
if (recheckRs.next()) {
|
||||
totalRecheckToday = recheckRs.getInt("recheck_count");
|
||||
}
|
||||
|
||||
// --- SEND ALL RESPONSES TO ALL CLIENTS ---
|
||||
int maxCurId = SessionManager.getMaxSessionId();
|
||||
for (int sessionId = 1; sessionId <= maxCurId; sessionId++) {
|
||||
// Send the queue update response
|
||||
UserUtil.sendPacket(sessionId, new GetCheckUpQueueUpdateResponse(resultArray));
|
||||
|
||||
// Send the patient count response
|
||||
UserUtil.sendPacket(sessionId, new TodayPatientCountResponse(totalPatientsToday));
|
||||
|
||||
// Send the new re-checkup count response
|
||||
UserUtil.sendPacket(sessionId, new RecheckCountResponse(totalRecheckToday));
|
||||
}
|
||||
log.info("send checkup queue update response to all clients");
|
||||
log.info("send today patient count response to all clients");
|
||||
log.info("send recheck count response to all clients");
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user