diff --git a/logs/bsk_client-1.log.gz b/logs/bsk_client-1.log.gz deleted file mode 100644 index 2c18212..0000000 Binary files a/logs/bsk_client-1.log.gz and /dev/null differ diff --git a/logs/bsk_server-1.log.gz b/logs/bsk_server-1.log.gz deleted file mode 100644 index 387e3ba..0000000 Binary files a/logs/bsk_server-1.log.gz and /dev/null differ diff --git a/logs/bsk_server-2.log.gz b/logs/bsk_server-2.log.gz deleted file mode 100644 index 49937ce..0000000 Binary files a/logs/bsk_server-2.log.gz and /dev/null differ diff --git a/logs/bsk_server-3.log.gz b/logs/bsk_server-3.log.gz deleted file mode 100644 index 347065b..0000000 Binary files a/logs/bsk_server-3.log.gz and /dev/null differ diff --git a/src/main/java/BsK/client/Client.java b/src/main/java/BsK/client/Client.java index 802b388..6413cad 100644 --- a/src/main/java/BsK/client/Client.java +++ b/src/main/java/BsK/client/Client.java @@ -1,7 +1,6 @@ package BsK.client; import BsK.client.network.handler.ClientHandler; -import BsK.client.ui.handler.UIHandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; diff --git a/src/main/java/BsK/client/network/handler/ClientHandler.java b/src/main/java/BsK/client/network/handler/ClientHandler.java index 29051af..152e862 100644 --- a/src/main/java/BsK/client/network/handler/ClientHandler.java +++ b/src/main/java/BsK/client/network/handler/ClientHandler.java @@ -6,12 +6,7 @@ import BsK.client.ui.handler.UIHandler; import BsK.common.entity.DoctorItem; import BsK.common.packet.Packet; import BsK.common.packet.PacketSerializer; -import BsK.common.packet.req.GetDoctorGeneralInfo; -import BsK.common.packet.req.GetMedInfoRequest; -import BsK.common.packet.req.GetProvinceRequest; -import BsK.common.packet.req.LoginRequest; import BsK.common.packet.res.*; -import BsK.common.util.network.NetworkUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.*; @@ -19,20 +14,11 @@ import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j; -import org.w3c.dom.Text; -import BsK.client.ui.component.CheckUpPage.AddDialog.AddDialog; -import BsK.client.ui.component.CheckUpPage.CheckUpPage; -import BsK.client.ui.component.LoginPage.LoginPage; -import BsK.client.ui.component.MainFrame; -import BsK.common.packet.res.GetDoctorGeneralInfoResponse; -import BsK.common.packet.res.GetMedInfoResponse; -import BsK.common.packet.res.GetOrderInfoByCheckupRes; @Slf4j public class ClientHandler extends SimpleChannelInboundHandler { 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 c11606a..30b8e27 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -1,9 +1,7 @@ package BsK.client.ui.component.CheckUpPage; import java.awt.Desktop; -import java.net.URI; -import BsK.client.Client; import BsK.client.LocalStorage; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; @@ -31,12 +29,9 @@ import lombok.extern.slf4j.Slf4j; import org.jdatepicker.impl.JDatePanelImpl; import org.jdatepicker.impl.JDatePickerImpl; import org.jdatepicker.impl.UtilDateModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.border.TitledBorder; -import javax.swing.border.Border; import javax.swing.table.DefaultTableModel; import javax.swing.RowSorter; import javax.swing.SortOrder; @@ -53,7 +48,6 @@ import javax.imageio.IIOImage; import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.KeyEvent; @@ -64,8 +58,6 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.datatransfer.*; import java.text.DecimalFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -84,15 +76,10 @@ import java.nio.file.WatchKey; import java.nio.file.WatchEvent; import java.nio.file.StandardWatchEventKinds; import java.nio.file.FileSystems; -import javax.imageio.ImageIO; import javax.swing.JColorChooser; // Webcam imports import com.github.sarxos.webcam.Webcam; -import com.github.sarxos.webcam.WebcamDiscoveryEvent; -import com.github.sarxos.webcam.WebcamDiscoveryListener; -import com.github.sarxos.webcam.WebcamEvent; -import com.github.sarxos.webcam.WebcamListener; import com.github.sarxos.webcam.WebcamPanel; import com.github.sarxos.webcam.WebcamResolution; @@ -100,9 +87,6 @@ import com.github.sarxos.webcam.WebcamResolution; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.Java2DFrameConverter; -import org.bytedeco.javacv.OpenCVFrameConverter; -import org.bytedeco.opencv.opencv_core.IplImage; -import static org.bytedeco.opencv.global.opencv_core.cvFlip; // Java imports for concurrent tasks import java.util.concurrent.CompletableFuture; @@ -111,11 +95,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeUnit; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import javax.swing.JFrame; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.text.StyledEditorKit; @@ -123,11 +105,9 @@ import javax.swing.text.rtf.RTFEditorKit; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; -import javax.swing.text.BadLocationException; import javax.swing.text.Document; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; -import java.io.OutputStream; import java.io.InputStream; import java.io.StringReader; @@ -139,23 +119,14 @@ import net.sf.jasperreports.view.JasperViewer; // --- End JasperReports Imports --- // Add new imports -import BsK.common.entity.PatientHistory; import BsK.common.packet.req.GetPatientHistoryRequest; import BsK.common.packet.res.GetPatientHistoryResponse; import BsK.common.packet.req.SaveCheckupRequest; -import BsK.common.packet.req.SearchAllRole; -import BsK.common.packet.req.SearchAllUser; import BsK.common.packet.req.UploadCheckupImageRequest; import BsK.common.packet.req.UploadCheckupPdfRequest; -import BsK.common.packet.res.AddCheckupResponse; -import BsK.common.packet.res.AddPatientResponse; -import BsK.common.packet.res.RegisterSuccessResponse; -import BsK.common.packet.res.SaveCheckupRes; import BsK.common.packet.res.UploadCheckupImageResponse; import BsK.common.packet.res.UploadCheckupPdfResponse; import BsK.common.util.date.DateUtils; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; import javax.swing.text.JTextComponent; @Slf4j @@ -226,10 +197,8 @@ public class CheckUpPage extends JPanel { private List selectedImagesForPrint = new ArrayList<>(); // New member variables for Supersonic View - private JPanel supersonicViewPanelGlobal; private JPanel imageGalleryPanel; // Displays thumbnails private JScrollPane imageGalleryScrollPane; - private JLabel webcamFeedLabel; // Placeholder for webcam feed private JComboBox webcamDeviceComboBox; private JButton webcamRefreshButton; private JButton takePictureButton; @@ -1077,7 +1046,7 @@ public class CheckUpPage extends JPanel { // addSelectAllOnFocus(notesField); // Do NOT select all for notes field // Set default font and size - notesField.setFont(new Font("Times New Roman", Font.PLAIN, 16)); + notesField.setFont(new Font("Arial", Font.PLAIN, 16)); // Set up RTF editor kit with proper character encoding RTFEditorKit rtfKit = new RTFEditorKit(); @@ -1088,7 +1057,7 @@ public class CheckUpPage extends JPanel { doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE); // Set up empty default template - String defaultTemplate = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\n" + + String defaultTemplate = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\n" + "{\\colortbl ;\\red0\\green0\\blue0;}\n" + "\\viewkind4\\uc1\\pard\\cf1\\f0\\fs32\\par}"; setRtfContentFromString(defaultTemplate); @@ -1103,7 +1072,7 @@ public class CheckUpPage extends JPanel { }); // Set default RTF template with proper Vietnamese character support - String defaultRtfTemplate = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\n" + + String defaultRtfTemplate = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\n" + "{\\colortbl ;\\red0\\green0\\blue0;}\n" + "\\viewkind4\\uc1\\pard\\cf1\\f0\\fs32\\par}"; try { @@ -1143,9 +1112,9 @@ public class CheckUpPage extends JPanel { underlineButton.setToolTipText("Gạch chân (Ctrl+U)"); // Font family selector - String[] fontFamilies = {"Arial", "Times New Roman", "Verdana", "Courier New", "Tahoma", "Calibri"}; + String[] fontFamilies = {"Arial", "Arial", "Verdana", "Courier New", "Tahoma", "Calibri"}; JComboBox fontFamilyComboBox = new JComboBox<>(fontFamilies); - fontFamilyComboBox.setSelectedItem("Times New Roman"); // Match default font of notesField + fontFamilyComboBox.setSelectedItem("Arial"); // Match default font of notesField fontFamilyComboBox.setFont(new Font("Arial", Font.PLAIN, 14)); fontFamilyComboBox.setToolTipText("Phông chữ"); fontFamilyComboBox.setPreferredSize(new Dimension(140, 25)); @@ -1236,7 +1205,7 @@ public class CheckUpPage extends JPanel { BorderFactory.createEtchedBorder(), "Đề nghị", TitledBorder.LEADING, TitledBorder.TOP, - new Font("Arial", Font.BOLD, 12), // Smaller font + new Font("Arial", Font.BOLD, 16), new Color(50, 50, 50) )); suggestionField = new JTextArea(2, 10); // Further reduced to 2 rows @@ -1322,11 +1291,11 @@ public class CheckUpPage extends JPanel { BorderFactory.createEtchedBorder(), "Chẩn đoán", TitledBorder.LEADING, TitledBorder.TOP, - new Font("Arial", Font.BOLD, 12), // Smaller font + new Font("Arial", Font.BOLD, 16), new Color(50, 50, 50) )); diagnosisField = new JTextArea(3, 20); // Reduced rows - diagnosisField.setFont(new Font("Arial", Font.PLAIN, 14)); // Slightly smaller font + diagnosisField.setFont(new Font("Arial", Font.BOLD, 16)); // Slightly smaller font diagnosisField.setLineWrap(true); diagnosisField.setWrapStyleWord(true); addSelectAllOnFocus(diagnosisField); @@ -1338,11 +1307,11 @@ public class CheckUpPage extends JPanel { BorderFactory.createEtchedBorder(), "Kết luận", TitledBorder.LEADING, TitledBorder.TOP, - new Font("Arial", Font.BOLD, 12), // Smaller font + new Font("Arial", Font.BOLD, 16), new Color(50, 50, 50) )); conclusionField = new JTextArea(3, 20); // Reduced rows - conclusionField.setFont(new Font("Arial", Font.PLAIN, 14)); // Slightly smaller font + conclusionField.setFont(new Font("Arial", Font.BOLD, 16)); // Slightly smaller font conclusionField.setLineWrap(true); conclusionField.setWrapStyleWord(true); addSelectAllOnFocus(conclusionField); @@ -1356,7 +1325,7 @@ public class CheckUpPage extends JPanel { // Create split pane for left and right panels JSplitPane contentSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel); - contentSplitPane.setResizeWeight(1.0); // Give maximum weight to the left panel + contentSplitPane.setResizeWeight(0.85); // Give maximum weight to the left panel contentSplitPane.setDividerSize(3); // Make divider even smaller contentSplitPane.setBorder(null); contentSplitPane.setOneTouchExpandable(true); // Add one-touch expand/collapse buttons @@ -2282,7 +2251,7 @@ public class CheckUpPage extends JPanel { } else { // If it's plain text, convert to RTF StringBuilder rtfContent = new StringBuilder(); - rtfContent.append("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\n"); + rtfContent.append("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\n"); rtfContent.append("{\\colortbl ;\\red0\\green0\\blue0;}\n"); rtfContent.append("\\viewkind4\\uc1\\pard\\cf1\\f0\\fs32 "); rtfContent.append(notes.replace("\n", "\\par ")); @@ -2291,7 +2260,7 @@ public class CheckUpPage extends JPanel { } } else { // Clear the notes field - setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); + setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); } statusComboBox.setSelectedItem(selectedPatient.getStatus()); @@ -4526,7 +4495,7 @@ public class CheckUpPage extends JPanel { // Convert plain text to RTF with proper Vietnamese encoding StringBuilder rtfContent = new StringBuilder(); - rtfContent.append("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\n"); + rtfContent.append("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\n"); rtfContent.append("{\\colortbl ;\\red0\\green0\\blue0;}\n"); rtfContent.append("\\viewkind4\\uc1\\pard\\cf1\\f0\\fs32 "); @@ -4812,7 +4781,7 @@ public class CheckUpPage extends JPanel { suggestionField.setText(""); diagnosisField.setText(""); conclusionField.setText(""); - setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Times New Roman;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); // Clear notes + setRtfContentFromString("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset163 Arial;}}\\viewkind4\\uc1\\pard\\f0\\fs32\\par}"); // Clear notes customerCccdDdcnField.setText(""); // Clear target address values 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 76c920f..a3bc86e 100644 --- a/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java +++ b/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java @@ -7,6 +7,7 @@ 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.ui.component.DashboardPage.DataViewerDialog.DataViewerDialog; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; import BsK.common.entity.Patient; @@ -432,8 +433,10 @@ public class DashboardPage extends JPanel { "Thông báo", JOptionPane.INFORMATION_MESSAGE); break; - case 3: - JOptionPane.showMessageDialog(this, "Chế độ khẩn cấp được kích hoạt!", "DỊCH VỤ", JOptionPane.WARNING_MESSAGE); + case 3: // View Medicine and Service Data + // --- MODIFIED LINE --- + DataViewerDialog dataViewerDialog = new DataViewerDialog(mainFrame); + dataViewerDialog.setVisible(true); break; } } diff --git a/src/main/java/BsK/client/ui/component/DashboardPage/DataViewerDialog/DataViewerDialog.java b/src/main/java/BsK/client/ui/component/DashboardPage/DataViewerDialog/DataViewerDialog.java new file mode 100644 index 0000000..2bb4fd8 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/DashboardPage/DataViewerDialog/DataViewerDialog.java @@ -0,0 +1,236 @@ +package BsK.client.ui.component.DashboardPage.DataViewerDialog; + +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.client.ui.component.common.RoundedPanel; +import BsK.common.entity.Medicine; +import BsK.common.entity.Service; +import BsK.common.packet.req.GetMedInfoRequest; +import BsK.common.packet.req.GetSerInfoRequest; +import BsK.common.packet.res.GetMedInfoResponse; +import BsK.common.packet.res.GetSerInfoResponse; +import BsK.common.util.network.NetworkUtil; +import BsK.common.util.text.TextUtils; +import lombok.extern.slf4j.Slf4j; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.JTableHeader; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class DataViewerDialog extends JDialog { + + // --- UI Components --- + private JTable medicineTable; + private DefaultTableModel medicineTableModel; + private JTextField medicineSearchField; + private JTable serviceTable; + private DefaultTableModel serviceTableModel; + private JTextField serviceSearchField; + + // --- Data Lists --- + private List allMedicines = new ArrayList<>(); + private List allServices = new ArrayList<>(); + + // --- Networking --- + private final ResponseListener getMedInfoResponseListener = this::handleGetMedInfoResponse; + private final ResponseListener getSerInfoResponseListener = this::handleGetSerInfoResponse; + + + public DataViewerDialog(Frame parent) { + super(parent, "Danh sách Thuốc & Dịch vụ", true); + initUI(); + setupNetworking(); + } + + private void initUI() { + setSize(1200, 700); + setLocationRelativeTo(getParent()); + setLayout(new BorderLayout()); + getContentPane().setBackground(new Color(245, 247, 250)); + + // Create the two main panels + JPanel medicinePanel = createListPanel("DANH SÁCH THUỐC", true); + JPanel servicePanel = createListPanel("DANH SÁCH DỊCH VỤ", false); + + // Create and configure the split pane + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, medicinePanel, servicePanel); + splitPane.setResizeWeight(0.5); // Equal initial size + splitPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + splitPane.setOpaque(false); + + add(splitPane, BorderLayout.CENTER); + } + + private JPanel createListPanel(String title, boolean isMedicine) { + RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false); + panel.setLayout(new BorderLayout(10, 10)); + + // Title and Search Bar Panel + JPanel topPanel = new JPanel(new BorderLayout(10, 0)); + topPanel.setOpaque(false); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JLabel searchIcon = new JLabel("🔍"); + searchIcon.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 18)); + + JTextField searchField = new JTextField(); + searchField.setFont(new Font("Segoe UI", Font.PLAIN, 16)); + + topPanel.add(searchIcon, BorderLayout.WEST); + topPanel.add(searchField, BorderLayout.CENTER); + + // Table Setup + DefaultTableModel tableModel = new DefaultTableModel() { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }; + JTable table = new JTable(tableModel); + setupTableStyle(table); + + if (isMedicine) { + this.medicineTable = table; + this.medicineTableModel = tableModel; + this.medicineSearchField = searchField; + medicineTableModel.setColumnIdentifiers(new String[]{"Mã", "Tên thuốc", "ĐVT", "Đơn giá"}); + table.getColumnModel().getColumn(0).setPreferredWidth(50); + table.getColumnModel().getColumn(1).setPreferredWidth(350); + } else { + this.serviceTable = table; + this.serviceTableModel = tableModel; + this.serviceSearchField = searchField; + serviceTableModel.setColumnIdentifiers(new String[]{"Mã", "Tên dịch vụ", "Đơn giá"}); + table.getColumnModel().getColumn(0).setPreferredWidth(50); + table.getColumnModel().getColumn(1).setPreferredWidth(350); + } + + searchField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + if (isMedicine) filterMedicineTable(); else filterServiceTable(); + } + @Override + public void removeUpdate(DocumentEvent e) { + if (isMedicine) filterMedicineTable(); else filterServiceTable(); + } + @Override + public void changedUpdate(DocumentEvent e) { + if (isMedicine) filterMedicineTable(); else filterServiceTable(); + } + }); + + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + + panel.add(topPanel, BorderLayout.NORTH); + panel.add(scrollPane, BorderLayout.CENTER); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEmptyBorder(10, 10, 10, 10), + title, + TitledBorder.CENTER, + TitledBorder.TOP, + new Font("Segoe UI", Font.BOLD, 18), + new Color(44, 82, 130) + )); + + return panel; + } + + private void setupTableStyle(JTable table) { + table.setFont(new Font("Segoe UI", Font.BOLD, 16)); + table.setRowHeight(35); + table.setSelectionBackground(new Color(219, 234, 254)); + table.setGridColor(new Color(229, 231, 235)); + table.setIntercellSpacing(new Dimension(0, 1)); + + JTableHeader header = table.getTableHeader(); + header.setFont(new Font("Segoe UI", Font.BOLD, 16)); + header.setBackground(new Color(243, 244, 246)); + header.setForeground(new Color(55, 65, 81)); + header.setReorderingAllowed(false); + } + + // --- Networking and Data Handling --- + + private void setupNetworking() { + ClientHandler.addResponseListener(GetMedInfoResponse.class, getMedInfoResponseListener); + ClientHandler.addResponseListener(GetSerInfoResponse.class, getSerInfoResponseListener); + + log.info("Requesting medicine and service lists for viewer."); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetMedInfoRequest()); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetSerInfoRequest()); + } + + private void handleGetMedInfoResponse(GetMedInfoResponse response) { + SwingUtilities.invokeLater(() -> { + allMedicines = response.getMedicines(); + filterMedicineTable(); + }); + } + + private void handleGetSerInfoResponse(GetSerInfoResponse response) { + SwingUtilities.invokeLater(() -> { + allServices = response.getServices(); + filterServiceTable(); + }); + } + + private void filterMedicineTable() { + String filterText = medicineSearchField.getText().trim(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); + medicineTableModel.setRowCount(0); + + if (allMedicines == null) return; + + for (Medicine med : allMedicines) { + boolean isDeleted = "1".equals(med.getDeleted()); + boolean nameMatches = filterText.isEmpty() || TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText); + + if (!isDeleted && nameMatches) { + medicineTableModel.addRow(new Object[]{ + med.getId(), + med.getName(), + med.getUnit(), + med.getSellingPrice() + }); + } + } + } + + private void filterServiceTable() { + String filterText = serviceSearchField.getText().trim(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); + serviceTableModel.setRowCount(0); + + if (allServices == null) return; + + for (Service service : allServices) { + boolean isDeleted = "1".equals(service.getDeleted()); + boolean nameMatches = filterText.isEmpty() || TextUtils.removeAccents(service.getName().toLowerCase()).contains(lowerCaseFilterText); + + if (!isDeleted && nameMatches) { + serviceTableModel.addRow(new Object[]{ + service.getId(), + service.getName(), + service.getCost() + }); + } + } + } + + @Override + public void dispose() { + log.info("Closing DataViewerDialog, removing listeners."); + ClientHandler.deleteListener(GetMedInfoResponse.class, getMedInfoResponseListener); + ClientHandler.deleteListener(GetSerInfoResponse.class, getSerInfoResponseListener); + super.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java b/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java index fc8bd2c..fd96a64 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java @@ -441,7 +441,7 @@ public class MedicineManagementPanel extends JPanel { TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14))); // --- Table Setup --- - String[] medicineColumns = {"ID", "Tên thuốc", "Bổ sung", "Trạng thái"}; + String[] medicineColumns = {"ID", "Tên thuốc", "Đơn vị", "Giá", "Bổ sung","Chú thích", "Trạng thái"}; medicineTableModel = new DefaultTableModel(medicineColumns, 0) { @Override public boolean isCellEditable(int row, int column) { return false; } @@ -555,7 +555,10 @@ public class MedicineManagementPanel extends JPanel { medicineTableModel.addRow(new Object[]{ med.getId(), med.getName(), + med.getUnit(), + med.getSellingPrice(), supplementStatus, + med.getPreferredNote(), deletedStatus }); } diff --git a/src/main/java/BsK/client/ui/component/DataDialog/UserManagementPanel.java b/src/main/java/BsK/client/ui/component/DataDialog/UserManagementPanel.java index 8759f4a..dfc5b5a 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/UserManagementPanel.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/UserManagementPanel.java @@ -1,6 +1,15 @@ package BsK.client.ui.component.DataDialog; +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.common.entity.User; +import BsK.common.packet.req.AddUserRequest; +import BsK.common.packet.req.EditUserRequest; +import BsK.common.packet.req.GetAllUserInfoRequest; +import BsK.common.packet.res.GetAllUserInfoResponse; +import BsK.common.util.network.NetworkUtil; import BsK.common.util.text.TextUtils; +import lombok.extern.slf4j.Slf4j; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -8,15 +17,11 @@ import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DocumentFilter; import java.awt.*; import java.util.ArrayList; import java.util.List; -import java.util.UUID; +@Slf4j public class UserManagementPanel extends JPanel { // --- UI Components --- @@ -25,26 +30,28 @@ public class UserManagementPanel extends JPanel { private JTextField userSearchField; // --- Input Fields --- - private JTextField userIdField; private JTextField usernameField; - private JTextField firstNameField; - private JTextField lastNameField; private JPasswordField passwordField; + private JTextField lastNameField; + private JTextField firstNameField; private JComboBox roleComboBox; - private JButton btnAdd, btnEdit, btnDelete, btnClear; + private JCheckBox chkIsDeleted; + private boolean isPasswordVisible = false; // State for password visibility + + // --- Action Buttons --- + private JButton btnAdd, btnEdit, btnClear; // --- Data & State --- private List allUsers = new ArrayList<>(); - private User selectedUser = null; + private String selectedUserId = null; - // A simple record to hold user data locally - private record User(String id, String username, String password, String firstName, String lastName, String role) {} + // --- Networking --- + private final ResponseListener getAllUserInfoListener = this::handleGetAllUserInfoResponse; public UserManagementPanel() { super(new BorderLayout(10, 10)); initComponents(); - createFakeData(); - filterUserTable(); // Initial population + setupNetworking(); } private void initComponents() { @@ -53,164 +60,158 @@ public class UserManagementPanel extends JPanel { this.add(createUserListPanel(), BorderLayout.CENTER); } - private void createFakeData() { - allUsers.add(new User(UUID.randomUUID().toString().substring(0, 8), "admin_user", "password123", "Admin", "Super", "ADMIN")); - allUsers.add(new User(UUID.randomUUID().toString().substring(0, 8), "doctor_phil", "pass", "Phil", "McGraw", "USER")); - allUsers.add(new User(UUID.randomUUID().toString().substring(0, 8), "nurse_jane", "pass", "Jane", "Doe", "USER")); + private void setupNetworking() { + ClientHandler.addResponseListener(GetAllUserInfoResponse.class, getAllUserInfoListener); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetAllUserInfoRequest()); } private JPanel createUserInputPanel() { JPanel mainInputPanel = new JPanel(new GridBagLayout()); - mainInputPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - GridBagConstraints mainGbc = new GridBagConstraints(); - mainGbc.fill = GridBagConstraints.HORIZONTAL; - mainGbc.anchor = GridBagConstraints.NORTHWEST; - mainGbc.insets = new Insets(5, 5, 5, 5); - mainGbc.weightx = 1.0; - mainGbc.gridx = 0; + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.NORTH; + gbc.insets = new Insets(0, 0, 10, 0); + gbc.weightx = 1.0; - Font titleFont = new Font("Arial", Font.BOLD, 14); - Font labelFont = new Font("Arial", Font.BOLD, 13); - Font textFont = new Font("Arial", Font.PLAIN, 13); - Dimension textFieldSize = new Dimension(100, 30); - - // --- User Details Panel --- - JPanel detailsPanel = new JPanel(new GridBagLayout()); - detailsPanel.setBorder(BorderFactory.createTitledBorder( + // --- User Info Panel --- + JPanel userInfoPanel = new JPanel(new GridBagLayout()); + userInfoPanel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Thông tin người dùng", - TitledBorder.LEADING, TitledBorder.TOP, titleFont, new Color(50, 50, 50) + TitledBorder.LEADING, TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) )); + addFormFields(userInfoPanel); + gbc.gridy = 0; + mainInputPanel.add(userInfoPanel, gbc); + + // --- Button Panel --- + gbc.gridy = 1; + mainInputPanel.add(createButtonPanel(), gbc); + + // --- Filler --- + gbc.gridy = 2; + gbc.weighty = 1.0; + mainInputPanel.add(new JPanel(), gbc); + + return mainInputPanel; + } + + private void addFormFields(JPanel panel) { GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.WEST; gbc.insets = new Insets(5, 5, 5, 5); + gbc.weightx = 1.0; - // User ID (Row 0) + // --- Row 0: Username --- gbc.gridy = 0; gbc.gridx = 0; - gbc.weightx = 0.2; // Label column width - JLabel userIdLabel = new JLabel("UserID:"); - userIdLabel.setFont(labelFont); - detailsPanel.add(userIdLabel, gbc); - + gbc.weightx = 0; + panel.add(new JLabel("Tên đăng nhập:"), gbc); gbc.gridx = 1; - gbc.weightx = 0.8; // Field column width - userIdField = new JTextField(); - userIdField.setEditable(false); - userIdField.setFont(textFont); - userIdField.setPreferredSize(textFieldSize); - detailsPanel.add(userIdField, gbc); + gbc.weightx = 1; + usernameField = new JTextField(15); + panel.add(usernameField, gbc); - // Username (Row 1) + // --- Row 1: Password with View Button --- gbc.gridy++; gbc.gridx = 0; - JLabel usernameLabel = new JLabel("Username:"); - usernameLabel.setFont(labelFont); - detailsPanel.add(usernameLabel, gbc); - + gbc.weightx = 0; + panel.add(new JLabel("Mật khẩu:"), gbc); gbc.gridx = 1; - usernameField = new JTextField(); - usernameField.setFont(textFont); - usernameField.setPreferredSize(textFieldSize); - ((AbstractDocument) usernameField.getDocument()).setDocumentFilter(new DocumentFilter() { - @Override - public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - super.replace(fb, offset, length, text.toLowerCase().replaceAll("[^a-z0-9_]", ""), attrs); + gbc.weightx = 1; + panel.add(createPasswordPanel(), gbc); // Use a helper method to create the composite panel + + // --- Row 2: Last Name --- + gbc.gridy++; + gbc.gridx = 0; + gbc.weightx = 0; + panel.add(new JLabel("Họ:"), gbc); + gbc.gridx = 1; + gbc.weightx = 1; + lastNameField = new JTextField(15); + panel.add(lastNameField, gbc); + + // --- Row 3: First Name --- + gbc.gridy++; + gbc.gridx = 0; + gbc.weightx = 0; + panel.add(new JLabel("Tên:"), gbc); + gbc.gridx = 1; + gbc.weightx = 1; + firstNameField = new JTextField(15); + panel.add(firstNameField, gbc); + + // --- Row 4: Role --- + gbc.gridy++; + gbc.gridx = 0; + gbc.weightx = 0; + panel.add(new JLabel("Vai trò:"), gbc); + gbc.gridx = 1; + gbc.weightx = 1; + roleComboBox = new JComboBox<>(new String[]{"ADMIN", "DOCTOR", "STAFF"}); + panel.add(roleComboBox, gbc); + } + + private JPanel createPasswordPanel() { + JPanel passwordPanel = new JPanel(new BorderLayout(5, 0)); + passwordField = new JPasswordField(15); + + JButton viewPasswordButton = new JButton(); + try { + ImageIcon viewIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/view.png"); + Image scaledImage = viewIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH); + viewPasswordButton.setIcon(new ImageIcon(scaledImage)); + } catch (Exception e) { + log.error("Could not load view icon", e); + viewPasswordButton.setText("👁️"); // Fallback emoji + } + + viewPasswordButton.setPreferredSize(new Dimension(30, 30)); + viewPasswordButton.setOpaque(false); + viewPasswordButton.setContentAreaFilled(false); + viewPasswordButton.setBorderPainted(false); + viewPasswordButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + viewPasswordButton.addActionListener(e -> { + isPasswordVisible = !isPasswordVisible; // Toggle the state + if (isPasswordVisible) { + passwordField.setEchoChar((char) 0); // Show password + } else { + passwordField.setEchoChar('•'); // Hide password } }); - detailsPanel.add(usernameField, gbc); - // First Name (Row 2) - gbc.gridy++; - gbc.gridx = 0; - JLabel firstNameLabel = new JLabel("Họ:"); - firstNameLabel.setFont(labelFont); - detailsPanel.add(firstNameLabel, gbc); - - gbc.gridx = 1; - firstNameField = new JTextField(); - firstNameField.setFont(textFont); - firstNameField.setPreferredSize(textFieldSize); - detailsPanel.add(firstNameField, gbc); - - // Last Name (Row 3) - gbc.gridy++; - gbc.gridx = 0; - JLabel lastNameLabel = new JLabel("Tên:"); - lastNameLabel.setFont(labelFont); - detailsPanel.add(lastNameLabel, gbc); - - gbc.gridx = 1; - lastNameField = new JTextField(); - lastNameField.setFont(textFont); - lastNameField.setPreferredSize(textFieldSize); - detailsPanel.add(lastNameField, gbc); - - // Role (Row 4) - gbc.gridy++; - gbc.gridx = 0; - JLabel roleLabel = new JLabel("Vai trò:"); - roleLabel.setFont(labelFont); - detailsPanel.add(roleLabel, gbc); - - gbc.gridx = 1; - roleComboBox = new JComboBox<>(new String[]{"USER", "ADMIN"}); - roleComboBox.setFont(textFont); - roleComboBox.setPreferredSize(textFieldSize); - detailsPanel.add(roleComboBox, gbc); - - // Password (Row 5) - gbc.gridy++; - gbc.gridx = 0; - JLabel passwordLabel = new JLabel("Mật khẩu:"); - passwordLabel.setFont(labelFont); - detailsPanel.add(passwordLabel, gbc); - - gbc.gridx = 1; - passwordField = new JPasswordField(); - passwordField.setFont(textFont); - passwordField.setPreferredSize(textFieldSize); - detailsPanel.add(passwordField, gbc); - - mainGbc.gridy = 0; - mainInputPanel.add(detailsPanel, mainGbc); - - mainGbc.gridy = 1; - mainInputPanel.add(createButtonPanel(), mainGbc); - - mainGbc.gridy = 2; - mainGbc.weighty = 1.0; // Filler to push content up - mainInputPanel.add(new JPanel(), mainGbc); - - return mainInputPanel; + passwordPanel.add(passwordField, BorderLayout.CENTER); + passwordPanel.add(viewPasswordButton, BorderLayout.EAST); + return passwordPanel; } private JPanel createButtonPanel() { JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); btnAdd = new JButton("Thêm mới"); btnEdit = new JButton("Chỉnh sửa"); - btnDelete = new JButton("Xoá"); btnClear = new JButton("Làm mới"); + chkIsDeleted = new JCheckBox("Xoá (Ẩn)"); + chkIsDeleted.setFont(new Font("Arial", Font.BOLD, 13)); Dimension btnSize = new Dimension(100, 35); btnAdd.setPreferredSize(btnSize); btnEdit.setPreferredSize(btnSize); - btnDelete.setPreferredSize(btnSize); btnClear.setPreferredSize(btnSize); + buttonPanel.add(chkIsDeleted); buttonPanel.add(btnAdd); buttonPanel.add(btnEdit); - buttonPanel.add(btnDelete); buttonPanel.add(btnClear); + // Button Actions btnClear.addActionListener(e -> clearUserFields()); - btnAdd.addActionListener(e -> handleAddUser()); - btnEdit.addActionListener(e -> handleEditUser()); - btnDelete.addActionListener(e -> handleDeleteUser()); + btnAdd.addActionListener(e -> addUser()); + btnEdit.addActionListener(e -> editUser()); - // Initial state btnEdit.setEnabled(false); - btnDelete.setEnabled(false); + chkIsDeleted.setEnabled(false); return buttonPanel; } @@ -218,142 +219,171 @@ public class UserManagementPanel extends JPanel { private JPanel createUserListPanel() { JPanel listPanel = new JPanel(new BorderLayout(10, 10)); listPanel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createEtchedBorder(), "Danh sách người dùng", - TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14))); + BorderFactory.createEtchedBorder(), "Danh Sách Người Dùng", + TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14)) + ); - // Search Panel - JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - searchPanel.add(new JLabel("Tìm theo username:")); - userSearchField = new JTextField(25); - userSearchField.getDocument().addDocumentListener(new DocumentListener() { - @Override public void insertUpdate(DocumentEvent e) { filterUserTable(); } - @Override public void removeUpdate(DocumentEvent e) { filterUserTable(); } - @Override public void changedUpdate(DocumentEvent e) { filterUserTable(); } - }); - searchPanel.add(userSearchField); - listPanel.add(searchPanel, BorderLayout.NORTH); - - // Table - String[] userColumns = {"UserID", "Username", "Họ", "Tên", "Vai trò"}; + String[] userColumns = {"ID", "Tên đăng nhập", "Họ và Tên", "Vai trò", "Trạng thái"}; userTableModel = new DefaultTableModel(userColumns, 0) { - @Override public boolean isCellEditable(int row, int column) { return false; } + @Override + public boolean isCellEditable(int row, int column) { return false; } }; userTable = new JTable(userTableModel); userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + userTable.setFont(new Font("Arial", Font.PLAIN, 12)); userTable.setRowHeight(28); userTable.getSelectionModel().addListSelectionListener(e -> { if (!e.getValueIsAdjusting() && userTable.getSelectedRow() != -1) { int viewRow = userTable.getSelectedRow(); String userId = (String) userTableModel.getValueAt(viewRow, 0); - selectedUser = allUsers.stream().filter(u -> u.id().equals(userId)).findFirst().orElse(null); - if (selectedUser != null) { - populateUserFields(selectedUser); - } + allUsers.stream() + .filter(user -> user.getId().equals(userId)) + .findFirst() + .ifPresent(this::populateUserFields); } }); + + JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + searchPanel.add(new JLabel("Tìm kiếm người dùng:")); + userSearchField = new JTextField(25); + searchPanel.add(userSearchField); + userSearchField.getDocument().addDocumentListener(new DocumentListener() { + @Override public void insertUpdate(DocumentEvent e) { filterUserTable(); } + @Override public void removeUpdate(DocumentEvent e) { filterUserTable(); } + @Override public void changedUpdate(DocumentEvent e) { filterUserTable(); } + }); + + listPanel.add(searchPanel, BorderLayout.NORTH); listPanel.add(new JScrollPane(userTable), BorderLayout.CENTER); + return listPanel; } + private void handleGetAllUserInfoResponse(GetAllUserInfoResponse response) { + SwingUtilities.invokeLater(() -> { + allUsers.clear(); + if (response != null && response.getUserInfo() != null) { + for (String[] userData : response.getUserInfo()) { + allUsers.add(new User(userData)); + } + } + filterUserTable(); + }); + } + private void filterUserTable() { - String filterText = userSearchField.getText().trim().toLowerCase(); + String filterText = userSearchField.getText().trim(); + String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); userTableModel.setRowCount(0); + for (User user : allUsers) { - if (filterText.isEmpty() || user.username().toLowerCase().contains(filterText)) { - userTableModel.addRow(new Object[]{user.id(), user.username(), user.firstName(), user.lastName(), user.role()}); + String fullName = user.getLastName() + " " + user.getFirstName(); + if (filterText.isEmpty() || + TextUtils.removeAccents(user.getUserName().toLowerCase()).contains(lowerCaseFilterText) || + TextUtils.removeAccents(fullName.toLowerCase()).contains(lowerCaseFilterText)) + { + String status = "0".equals(user.getDeleted()) ? "Hoạt động" : "Đã ẩn"; + userTableModel.addRow(new Object[]{ + user.getId(), + user.getUserName(), + fullName, + user.getRole(), + status + }); } } } private void populateUserFields(User user) { - userIdField.setText(user.id()); - usernameField.setText(user.username()); - firstNameField.setText(user.firstName()); - lastNameField.setText(user.lastName()); - roleComboBox.setSelectedItem(user.role()); - passwordField.setText(""); - passwordField.setEnabled(false); + if (user == null) return; + selectedUserId = user.getId(); + usernameField.setText(user.getUserName()); + lastNameField.setText(user.getLastName()); + firstNameField.setText(user.getFirstName()); + roleComboBox.setSelectedItem(user.getRole()); + chkIsDeleted.setSelected("1".equals(user.getDeleted())); + + // --- Password field population --- + isPasswordVisible = false; // Reset visibility state + passwordField.setEchoChar('•'); // Ensure it's hidden + passwordField.setText(user.getPassword()); // Populate with actual password + btnAdd.setEnabled(false); btnEdit.setEnabled(true); - btnDelete.setEnabled(true); + chkIsDeleted.setEnabled(true); } private void clearUserFields() { - selectedUser = null; - userIdField.setText("(Tự động)"); + selectedUserId = null; usernameField.setText(""); - firstNameField.setText(""); lastNameField.setText(""); + firstNameField.setText(""); roleComboBox.setSelectedIndex(0); + chkIsDeleted.setSelected(false); + + // --- Clear password and reset visibility --- + isPasswordVisible = false; + passwordField.setEchoChar('•'); passwordField.setText(""); - passwordField.setEnabled(true); + userTable.clearSelection(); + usernameField.requestFocusInWindow(); + btnAdd.setEnabled(true); btnEdit.setEnabled(false); - btnDelete.setEnabled(false); - usernameField.requestFocusInWindow(); + chkIsDeleted.setEnabled(false); } - private void handleAddUser() { - String username = usernameField.getText().trim(); + private void addUser() { + String userName = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); - if (username.isEmpty() || password.isEmpty()) { - JOptionPane.showMessageDialog(this, "Username và Mật khẩu không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + if (userName.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(this, "Tên đăng nhập và mật khẩu không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); return; } - if (allUsers.stream().anyMatch(u -> u.username().equals(username))) { - JOptionPane.showMessageDialog(this, "Username đã tồn tại.", "Lỗi", JOptionPane.ERROR_MESSAGE); - return; - } - User newUser = new User( - UUID.randomUUID().toString().substring(0, 8), - username, - password, // In a real app, hash this! - firstNameField.getText().trim(), + + AddUserRequest request = new AddUserRequest( + userName, + password, lastNameField.getText().trim(), - (String) roleComboBox.getSelectedItem() + firstNameField.getText().trim(), + (String) roleComboBox.getSelectedItem(), + false // New users are never deleted by default ); - allUsers.add(newUser); - filterUserTable(); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + JOptionPane.showMessageDialog(this, "Yêu cầu thêm người dùng '" + userName + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); clearUserFields(); - JOptionPane.showMessageDialog(this, "Thêm người dùng thành công!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); } - private void handleEditUser() { - if (selectedUser == null) return; - // Find the user in the list and update them - for (int i = 0; i < allUsers.size(); i++) { - if (allUsers.get(i).id().equals(selectedUser.id())) { - User updatedUser = new User( - selectedUser.id(), - usernameField.getText().trim(), - selectedUser.password(), // Keep original password - firstNameField.getText().trim(), - lastNameField.getText().trim(), - (String) roleComboBox.getSelectedItem() - ); - allUsers.set(i, updatedUser); - break; - } + private void editUser() { + if (selectedUserId == null) { + JOptionPane.showMessageDialog(this, "Vui lòng chọn người dùng để chỉnh sửa.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; } - filterUserTable(); + String userName = usernameField.getText().trim(); + if (userName.isEmpty()) { + JOptionPane.showMessageDialog(this, "Tên đăng nhập không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + + // Only send a password if the field is not empty, otherwise send empty string + String password = new String(passwordField.getPassword()); + + EditUserRequest request = new EditUserRequest( + selectedUserId, + userName, + password, // Server should handle if this is empty (i.e., don't change password) + lastNameField.getText().trim(), + firstNameField.getText().trim(), + (String) roleComboBox.getSelectedItem(), + chkIsDeleted.isSelected() + ); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + JOptionPane.showMessageDialog(this, "Yêu cầu cập nhật người dùng '" + userName + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); clearUserFields(); - JOptionPane.showMessageDialog(this, "Cập nhật thành công!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); } - - private void handleDeleteUser() { - if (selectedUser == null) return; - int choice = JOptionPane.showConfirmDialog(this, - "Bạn có chắc chắn muốn xóa người dùng '" + selectedUser.username() + "' không?", - "Xác nhận xóa", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (choice == JOptionPane.YES_OPTION) { - allUsers.removeIf(u -> u.id().equals(selectedUser.id())); - filterUserTable(); - clearUserFields(); - } - } - + public void cleanup() { - // No listeners to clean up yet, but good practice to have. + ClientHandler.deleteListener(GetAllUserInfoResponse.class, getAllUserInfoListener); } -} +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/LandingPage/LandingPage.java b/src/main/java/BsK/client/ui/component/LandingPage/LandingPage.java index f9ef2ca..6d32ea3 100644 --- a/src/main/java/BsK/client/ui/component/LandingPage/LandingPage.java +++ b/src/main/java/BsK/client/ui/component/LandingPage/LandingPage.java @@ -63,15 +63,19 @@ public class LandingPage extends JPanel { gbc.gridx = 0; gbc.gridy = 0; + // --- BUTTONS UPDATED --- + Color primaryBlueText = new Color(13, 110, 253); + JButton loginButton = new JButton("Đăng nhập"); - loginButton.setUI(new RoundedButtonUI(new Color(0, 0, 0, 0), Color.WHITE, Color.WHITE, 30)); + loginButton.setForeground(primaryBlueText); // Set text color + loginButton.setUI(new RoundedButtonUI(Color.WHITE, primaryBlueText, 10)); // White background loginButton.setFont(new Font("Arial", Font.BOLD, 14)); JButton registerButton = new JButton("Đăng ký"); - registerButton.setUI(new RoundedButtonUI(new Color(0, 0, 0, 0), Color.WHITE, Color.WHITE, 30)); + registerButton.setForeground(primaryBlueText); // Set text color + registerButton.setUI(new RoundedButtonUI(Color.WHITE, primaryBlueText, 10)); // White background registerButton.setFont(new Font("Arial", Font.BOLD, 14)); - // Set the same preferred size for both buttons Dimension buttonSize = new Dimension(150, 40); loginButton.setPreferredSize(buttonSize); @@ -86,7 +90,6 @@ public class LandingPage extends JPanel { // Action listeners for buttons loginButton.addActionListener(e -> mainFrame.showPage("LoginPage")); - registerButton.addActionListener(e -> mainFrame.showPage("RegisterPage")); } -} +} \ No newline at end of file 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 722ccd9..2eafcf2 100644 --- a/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java +++ b/src/main/java/BsK/client/ui/component/LoginPage/LoginPage.java @@ -79,14 +79,44 @@ public class LoginPage extends JPanel { JPasswordField passwordField = new JPasswordField(20); formPanel.add(passwordField, gbc); + // --- BUTTON PANEL START --- gbc.gridx = 0; gbc.gridy++; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.CENTER; + gbc.insets = new Insets(20, 10, 10, 10); // Add top margin + + JPanel buttonContainer = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0)); + buttonContainer.setOpaque(false); + + // --- Back Button (Secondary Style) --- + JButton backButton = new JButton("Trở về"); + backButton.setForeground(Color.WHITE); + // Style: Transparent background with a white border + backButton.setUI(new RoundedButtonUI(new Color(0, 0, 0, 0), Color.WHITE, Color.WHITE, 10)); + backButton.setFont(new Font("Arial", Font.BOLD, 14)); + backButton.addActionListener(e -> mainFrame.showPage("LandingPage")); + + // --- Login Button (Primary Style, copied from LandingPage) --- + Color primaryBlueText = new Color(13, 110, 253); JButton loginButton = new JButton("Đăng nhập"); - loginButton.setUI(new RoundedButtonUI(new Color(13, 110, 253), Color.WHITE, 10)); + loginButton.setForeground(primaryBlueText); + loginButton.setUI(new RoundedButtonUI(Color.WHITE, primaryBlueText, 10)); // White background loginButton.setFont(new Font("Arial", Font.BOLD, 14)); + Dimension buttonSize = new Dimension(120, 40); + loginButton.setPreferredSize(buttonSize); + backButton.setPreferredSize(buttonSize); + + buttonContainer.add(backButton); + buttonContainer.add(loginButton); + + formPanel.add(buttonContainer, gbc); + // --- BUTTON PANEL END --- + + backgroundPanel.add(formPanel, new GridBagConstraints()); + + passwordField.addActionListener(e -> loginButton.doClick()); loginButton.addActionListener(e -> { @@ -97,14 +127,13 @@ public class LoginPage extends JPanel { NetworkUtil.sendPacket(ClientHandler.ctx.channel(), loginRequest); }); - ClientHandler.addResponseListener(LoginSuccessResponse.class, response -> { log.info("Login successful, UserId: {}, Role: {}", response.getUserId(), response.getRole()); LocalStorage.username = loginField.getText(); LocalStorage.userId = response.getUserId(); - 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 + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new ClinicInfoRequest()); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetDoctorGeneralInfo()); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetProvinceRequest()); ClientHandler.deleteListener(ErrorResponse.class); mainFrame.showPage("DashboardPage"); }); @@ -114,21 +143,5 @@ public class LoginPage extends JPanel { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, "Đăng nhập thất bại: " + response.getError(), "Lỗi", JOptionPane.ERROR_MESSAGE)); }); - - - formPanel.add(loginButton, gbc); - backgroundPanel.add(formPanel, new GridBagConstraints()); - - - JButton backButton = new JButton("Trở về"); - backButton.setUI(new RoundedButtonUI(new Color(108, 117, 125), Color.WHITE, 10)); - backButton.setFont(new Font("Arial", Font.BOLD, 14)); - - backButton.addActionListener(e -> mainFrame.showPage("LandingPage")); - - JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - southPanel.setOpaque(false); - southPanel.add(backButton); - add(southPanel, BorderLayout.SOUTH); } -} +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java index 8bb06d2..7e8ed8a 100644 --- a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java +++ b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java @@ -725,7 +725,7 @@ public class MedicineDialog extends JDialog { quantitySpinner.setValue(1); - String preferenceNote = medicine.getPreferenceNote(); + String preferenceNote = medicine.getPreferredNote(); if (preferenceNote != null && !preferenceNote.trim().isEmpty()) { String[] parts = preferenceNote.split(",", 4); try { diff --git a/src/main/java/BsK/common/entity/Medicine.java b/src/main/java/BsK/common/entity/Medicine.java index cc6d307..6c9d678 100644 --- a/src/main/java/BsK/common/entity/Medicine.java +++ b/src/main/java/BsK/common/entity/Medicine.java @@ -17,7 +17,7 @@ public class Medicine { private String description; private String unit; private String sellingPrice; - private String preferenceNote; + private String preferredNote; private String supplement; // 0: no, 1: yes private String deleted; // 0: no, 1: yes @@ -35,7 +35,7 @@ public class Medicine { this.description = data[3]; this.unit = data[4]; this.sellingPrice = data[5]; - this.preferenceNote = data[6]; + this.preferredNote = data[6]; this.supplement = data[7]; this.deleted = data[8]; } @@ -45,6 +45,6 @@ public class Medicine { * @return String array representation of Medicine */ public String[] toStringArray() { - return new String[]{id, name, company, description, unit, sellingPrice, preferenceNote, supplement, deleted}; + return new String[]{id, name, company, description, unit, sellingPrice, preferredNote, supplement, deleted}; } } \ No newline at end of file diff --git a/src/main/java/BsK/common/entity/User.java b/src/main/java/BsK/common/entity/User.java new file mode 100644 index 0000000..cb79d08 --- /dev/null +++ b/src/main/java/BsK/common/entity/User.java @@ -0,0 +1,32 @@ +package BsK.common.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class User { + private String id; + private String userName; + private String lastName; + private String firstName; + private String password; + private String role; + private String deleted; // "0" or "1" + + /** + * Constructor to create a User from a String array from the server. + */ + public User(String[] data) { + if (data.length < 7) { + throw new IllegalArgumentException("User data array must contain at least 7 elements"); + } + this.id = data[0]; + this.userName = data[1]; + this.lastName = data[2]; + this.firstName = data[3]; + this.password = data[4]; + this.role = data[5]; + this.deleted = data[6]; + } +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/AddUserRequest.java b/src/main/java/BsK/common/packet/req/AddUserRequest.java new file mode 100644 index 0000000..1b84ee8 --- /dev/null +++ b/src/main/java/BsK/common/packet/req/AddUserRequest.java @@ -0,0 +1,16 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class AddUserRequest implements Packet { + private String userName; + private String password; + private String lastName; + private String firstName; + private String role; + private Boolean deleted; // Typically false for new users +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/EditUserRequest.java b/src/main/java/BsK/common/packet/req/EditUserRequest.java new file mode 100644 index 0000000..8a86f7f --- /dev/null +++ b/src/main/java/BsK/common/packet/req/EditUserRequest.java @@ -0,0 +1,17 @@ +package BsK.common.packet.req; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EditUserRequest implements Packet { + private String id; + private String userName; + private String password; // Can be empty/null if not changing + private String lastName; + private String firstName; + private String role; + private Boolean deleted; +} \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/req/GetAllUserRequest.java b/src/main/java/BsK/common/packet/req/GetAllUserInfoRequest.java similarity index 71% rename from src/main/java/BsK/common/packet/req/GetAllUserRequest.java rename to src/main/java/BsK/common/packet/req/GetAllUserInfoRequest.java index 4d1be7d..b70215c 100644 --- a/src/main/java/BsK/common/packet/req/GetAllUserRequest.java +++ b/src/main/java/BsK/common/packet/req/GetAllUserInfoRequest.java @@ -6,6 +6,6 @@ import lombok.Data; @Data @AllArgsConstructor -public class GetAllUserRequest implements Packet { - +public class GetAllUserInfoRequest implements Packet { + } diff --git a/src/main/java/BsK/common/packet/res/GetAllUserInfoResponse.java b/src/main/java/BsK/common/packet/res/GetAllUserInfoResponse.java new file mode 100644 index 0000000..bcfc891 --- /dev/null +++ b/src/main/java/BsK/common/packet/res/GetAllUserInfoResponse.java @@ -0,0 +1,11 @@ +package BsK.common.packet.res; + +import BsK.common.packet.Packet; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class GetAllUserInfoResponse implements Packet { + private String[][] userInfo; +} diff --git a/src/main/java/BsK/server/network/entity/User.java b/src/main/java/BsK/server/network/entity/User.java index 32de1f1..d03834e 100644 --- a/src/main/java/BsK/server/network/entity/User.java +++ b/src/main/java/BsK/server/network/entity/User.java @@ -36,6 +36,10 @@ public class User { role = Role.valueOf(rs.getString("role_name")); userId = rs.getInt("user_id"); // Update the client connection with the new role + Boolean deleted = rs.getBoolean("deleted"); + if (deleted) { + role = Role.GUEST; + } SessionManager.updateUserRole(channel.id().asLongText(), role.toString(), userId); } } diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index b8b039c..879918e 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -1545,43 +1545,92 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); - // while (rs.next()) { - // String userId = rs.getString("user_id"); - // String userName = rs.getString("user_name"); - // String lastName = rs.getString("last_name"); - // String firstName = rs.getString("first_name"); - // String role = rs.getString("role"); - // String result = String.join("|", userId, userName, lastName, firstName, role); - // 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("\\|"); - // } - // UserUtil.sendPacket(sessionId, new GetAllUserResponse(resultArray)); - // } catch (SQLException e) { - // throw new RuntimeException(e); - // } - // } + + private void getAllUser(int sessionId) { + try { + String sql = "SELECT user_id, user_name, last_name, password, first_name, role_name, deleted FROM User"; + PreparedStatement stmt = Server.connection.prepareStatement(sql); + ResultSet rs = stmt.executeQuery(); + ArrayList resultList = new ArrayList<>(); + while (rs.next()) { + String userId = rs.getString("user_id"); + String userName = rs.getString("user_name"); + String lastName = rs.getString("last_name"); + String firstName = rs.getString("first_name"); + String password = rs.getString("password"); + String role = rs.getString("role_name"); + String deleted = rs.getString("deleted"); + String result = String.join("|", userId, userName, lastName, firstName, password, role, deleted); + resultList.add(result); + } + String[] resultString = resultList.toArray(new String[0]); + String[][] resultArray = new String[resultString.length][]; + for (int i = 0; i < resultString.length; i++) { + resultArray[i] = resultString[i].split("\\|"); + } + UserUtil.sendPacket(sessionId, new GetAllUserInfoResponse(resultArray)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } private void getSerInfo(int sessionId) { try { diff --git a/src/main/resources/database/BSK.db b/src/main/resources/database/BSK.db index 5853655..0969e7e 100644 Binary files a/src/main/resources/database/BSK.db and b/src/main/resources/database/BSK.db differ