diff --git a/medical_invoice.pdf b/medical_invoice.pdf index dd4f5da..d38a18b 100644 Binary files a/medical_invoice.pdf and b/medical_invoice.pdf differ diff --git a/src/main/java/BsK/client/ui/assets/icon/cashier-machine.png b/src/main/java/BsK/client/ui/assets/icon/cashier-machine.png new file mode 100644 index 0000000..58c827a Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/cashier-machine.png differ diff --git a/src/main/java/BsK/client/ui/assets/icon/dashboard.png b/src/main/java/BsK/client/ui/assets/icon/dashboard.png new file mode 100644 index 0000000..1107002 Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/dashboard.png differ diff --git a/src/main/java/BsK/client/ui/assets/icon/database.png b/src/main/java/BsK/client/ui/assets/icon/database.png new file mode 100644 index 0000000..c0e903e Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/database.png differ diff --git a/src/main/java/BsK/client/ui/assets/icon/health-check.png b/src/main/java/BsK/client/ui/assets/icon/health-check.png new file mode 100644 index 0000000..180fd68 Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/health-check.png differ diff --git a/src/main/java/BsK/client/ui/assets/icon/info.png b/src/main/java/BsK/client/ui/assets/icon/info.png new file mode 100644 index 0000000..a762a6c Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/info.png differ diff --git a/src/main/java/BsK/client/ui/assets/icon/warehouse.png b/src/main/java/BsK/client/ui/assets/icon/warehouse.png new file mode 100644 index 0000000..7cb1d31 Binary files /dev/null and b/src/main/java/BsK/client/ui/assets/icon/warehouse.png differ diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java index 2a04f65..e2f1289 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -26,6 +26,7 @@ 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.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; @@ -73,6 +74,7 @@ public class CheckUpPage extends JPanel { private JComboBox callRoomComboBox; private JButton callPatientButton; private JLabel callingStatusLabel; + private JLabel activeNavItem = null; // To keep track of the active navigation item private QueueViewPage tvQueueFrame; @@ -157,49 +159,99 @@ public class CheckUpPage extends JPanel { JPanel navItemsPanel = new JPanel(); navItemsPanel.setBackground(new Color(63, 81, 181)); - navItemsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 10)); + navItemsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 15)); // Increased vgap to 15 navItemsPanel.setAlignmentY(Component.CENTER_ALIGNMENT); - String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Kho", "Người dùng", "Thông tin"}; - String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "InventoryPage", "UserPage", "InfoPage"}; + String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Kho", "Thanh toán", "Người dùng", "Thông tin"}; + String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "InventoryPage", "CheckoutPage", "UserPage", "InfoPage"}; + String[] iconFiles = {"dashboard.png", "health-check.png", "database.png", "warehouse.png", "cashier-machine.png", "user.png", "info.png"}; // Icon files corresponding to items + + final Color defaultNavColor = new Color(63, 81, 181); // Base navbar color (transparent items) + final Color hoverNavColor = new Color(50, 70, 170); // Darker for hover + final Color activeNavColor = new Color(33, 150, 243); // Existing active/click color + + // Define borders + final Border defaultNavItemBorder = BorderFactory.createEmptyBorder(12, 15, 12, 15); // Increased top/bottom padding to 12 + final Border activePageSpecificBorder = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(0, 0, 3, 0, activeNavColor), + BorderFactory.createEmptyBorder(12, 15, 9, 15) // Adjusted padding for the border (12 top, 9 bottom to account for 3px border) + ); + for (int i = 0; i < navBarItems.length; i++) { - String item = navBarItems[i]; - String dest = destination[i]; - JLabel label = new JLabel(item); + final String itemText = navBarItems[i]; + final String dest = destination[i]; + String iconFileName = iconFiles[i]; + + final JLabel label = new JLabel(itemText); label.setForeground(Color.WHITE); label.setHorizontalAlignment(SwingConstants.CENTER); - label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - label.setFont(new Font("Arial", Font.BOLD, 14)); - label.setBorder(BorderFactory.createEmptyBorder(7, 15, 10, 15)); + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + label.setFont(new Font("Arial", Font.BOLD, 12)); + label.setBorder(defaultNavItemBorder); // Apply default padding + label.setOpaque(false); // Default is transparent - if (item.equals("Thăm khám")) { - label.setBorder(BorderFactory.createMatteBorder(0, 0, 5, 0, new Color(33, 150, 243))); + try { + String iconPath = "src/main/java/BsK/client/ui/assets/icon/" + iconFileName; + ImageIcon originalIcon = new ImageIcon(iconPath); + Image scaledImage = originalIcon.getImage().getScaledInstance(36, 36, Image.SCALE_SMOOTH); // Icon size 36x36 + ImageIcon scaledIcon = new ImageIcon(scaledImage); + label.setIcon(scaledIcon); + } catch (Exception e) { + log.error("Error loading icon: {} for nav item: {}", iconFileName, itemText, e); } + + label.setVerticalTextPosition(SwingConstants.BOTTOM); + label.setHorizontalTextPosition(SwingConstants.CENTER); + + // Initial active state for "Thăm khám" + if (itemText.equals("Thăm khám")) { + label.setBorder(activePageSpecificBorder); + label.setBackground(activeNavColor); // Set background for active + label.setOpaque(true); + activeNavItem = label; // Set as initially active + } + label.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mouseClicked(java.awt.event.MouseEvent e) { - mainFrame.showPage(dest); - for (Component comp : navItemsPanel.getComponents()) { - JLabel lbl = (JLabel) comp; - lbl.setBackground(null); - lbl.setOpaque(false); + if (activeNavItem != null && activeNavItem != label) { + // Reset previous active item + activeNavItem.setBackground(defaultNavColor); // Or null for transparent against navBar + activeNavItem.setOpaque(false); + activeNavItem.setForeground(Color.WHITE); + activeNavItem.setBorder(defaultNavItemBorder); // Reset to default border } - label.setBackground(new Color(33, 150, 243)); - label.setOpaque(true); - label.setForeground(Color.WHITE); + + activeNavItem = label; + // Apply active styling + activeNavItem.setBackground(activeNavColor); + activeNavItem.setOpaque(true); + activeNavItem.setForeground(Color.WHITE); // Ensure text is white on active background + if (itemText.equals("Thăm khám")) { // Specific border for "Thăm khám" page + activeNavItem.setBorder(activePageSpecificBorder); + } else { + // For other items, active state might just be the background + default padding + // Or a generic active border if desired. For now, background is the main indicator. + activeNavItem.setBorder(defaultNavItemBorder); // Ensure it has correct padding + } + mainFrame.showPage(dest); } + @Override public void mouseEntered(java.awt.event.MouseEvent e) { - label.setForeground(new Color(200, 230, 255)); - label.setBackground(new Color(33, 150, 243)); - label.setOpaque(true); + if (label != activeNavItem) { + label.setForeground(new Color(200, 230, 255)); + label.setBackground(hoverNavColor); + label.setOpaque(true); + } } + @Override public void mouseExited(java.awt.event.MouseEvent e) { - if (label.getBackground() != new Color(33, 150, 243)) { - label.setForeground(Color.WHITE); - label.setBackground(null); - label.setOpaque(false); + if (label != activeNavItem) { + label.setForeground(Color.WHITE); + label.setBackground(defaultNavColor); // Or null + label.setOpaque(false); } } }); @@ -254,7 +306,7 @@ public class CheckUpPage extends JPanel { } }); navBar.add(welcomeLabel, BorderLayout.EAST); - navBar.setPreferredSize(new Dimension(1200, 50)); + navBar.setPreferredSize(new Dimension(1200, 85)); // Increased preferred height to 85 // Panels RoundedPanel leftPanel = new RoundedPanel(20, Color.WHITE, false); @@ -534,7 +586,7 @@ public class CheckUpPage extends JPanel { genderComboBox.getSelectedItem().toString(), customerAddressField.getText() + ", " + (wardComboBox.getSelectedItem() != null ? wardComboBox.getSelectedItem().toString() : "") + ", " + (districtComboBox.getSelectedItem() != null ? districtComboBox.getSelectedItem().toString() : "") + ", " + (provinceComboBox.getSelectedItem() != null ? provinceComboBox.getSelectedItem().toString() : ""), doctorComboBox.getSelectedItem().toString(), diagnosisField.getText(), - notesField.getText(), medicinePrescription); + notesField.getText(), medicinePrescription, servicePrescription); medicineInvoice.createDialog(mainFrame); break; default: diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/EditableMedicalInvoice.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/EditableMedicalInvoice.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java index 7343dd2..92d0178 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java @@ -29,6 +29,7 @@ import java.io.*; import java.util.ArrayList; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.text.DecimalFormat; public class MedicineInvoice{ @@ -44,6 +45,8 @@ public class MedicineInvoice{ private String date; private String id; private String[][] med; // Name, Quantity, Price + private String[][] services; // Name, Quantity, Price + private static final DecimalFormat vndFormatter = new DecimalFormat("#,##0"); public static void main(String[] args) { SwingUtilities.invokeLater(() -> { @@ -51,10 +54,15 @@ public class MedicineInvoice{ openDialogButton.addActionListener(e -> new MedicineInvoice("1212", "Nguyễn Văn A", "01/01/1990", "0123456789", "Nam", "123 Đường ABC, Quận XYZ, TP HCM", "Bác sĩ XYZ", "Sốt cao", - "Nghỉ ngơi nhiều, uống nhiều nước", new String[][]{ + "Nghỉ ngơi nhiều, uống nhiều nước", + new String[][]{ {"Paracetamol", "2", "5000"}, {"Vitamin C", "1", "10000"}, {"Amoxicillin", "1", "20000"} + }, + new String[][] { + {"Khám tổng quát", "1", "150000"}, + {"Xét nghiệm máu", "1", "80000"} } ).createDialog(null)); JFrame frame = new JFrame("Main Application"); @@ -68,7 +76,7 @@ public class MedicineInvoice{ public MedicineInvoice(String id, String patientName, String patientDOB, String patientPhone, String patientGender, String patientAddress, String doctorName, String diagnosis, - String notes, String[][] med) { + String notes, String[][] med, String[][] services) { this.id = id; this.patientName = patientName; this.patientDOB = patientDOB; @@ -87,13 +95,14 @@ public class MedicineInvoice{ this.date = formattedDate; this.med = med; + this.services = services; } public void createDialog(JFrame parent) { dialog = new JDialog(parent, "Medicine Invoice", true); dialog.setSize(600, 600); - dialog.setResizable(false); + dialog.setResizable(true); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setLayout(new BorderLayout()); @@ -163,98 +172,246 @@ public class MedicineInvoice{ private void generatePdf(String pdfPath) throws Exception { PdfWriter writer = new PdfWriter(pdfPath); PdfDocument pdfDoc = new PdfDocument(writer); - Document document = new Document(pdfDoc); + Document document = new Document(pdfDoc); // Default A4 size + // Set document margins - REDUCED + document.setMargins(20, 25, 20, 25); // Smaller margins (top, right, bottom, left) - String boldFontPath = "src/main/java/BsK/client/ui/assets/font/SVN-Arial Bold.ttf"; // Update with your font path + String boldFontPath = "src/main/java/BsK/client/ui/assets/font/SVN-Arial Bold.ttf"; PdfFont boldFont = PdfFontFactory.createFont(boldFontPath, "Identity-H", true); - String fontPath = "src/main/java/BsK/client/ui/assets/font/SVN-Arial Regular.ttf"; // Update with your font path + String fontPath = "src/main/java/BsK/client/ui/assets/font/SVN-Arial Regular.ttf"; PdfFont font = PdfFontFactory.createFont(fontPath, "Identity-H", true); - // Add logo - String logoPath = "src/main/java/BsK/client/ui/assets/icon/add.png"; // Path to your logo - ImageData imageData = ImageDataFactory.create(logoPath); - Image logo = new Image(imageData).scaleToFit(75, 75); - logo.setFixedPosition(50, 750); - document.add(logo); + // --- HEADER SECTION --- + Table headerTable = new Table(UnitValue.createPercentArray(new float[]{1.5f, 3.5f})) + .setWidth(UnitValue.createPercentValue(100)) + .setBorder(Border.NO_BORDER) + .setMarginBottom(5); // Reduced margin bottom - // Add clinic information - String clinicInfo = String.format("%s\n%s\nSố điện thoại: %s\nNgày: %s", LocalStorage.ClinicName, - LocalStorage.ClinicAddress, LocalStorage.ClinicPhone, date); - Paragraph clinicInfoParagraph = new Paragraph(clinicInfo) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT) + try { + String logoPath = "src/main/java/BsK/client/ui/assets/icon/clinic_logo.png"; + ImageData imageData = ImageDataFactory.create(logoPath); + Image logo = new Image(imageData).scaleToFit(60, 60).setMarginRight(15); // Slightly smaller logo & margin + headerTable.addCell(new Cell().add(logo).setBorder(Border.NO_BORDER).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + } catch (Exception e) { + headerTable.addCell(new Cell().setBorder(Border.NO_BORDER)); + System.err.println("Logo image not found or error loading: " + e.getMessage()); + } + + Paragraph clinicInfoParagraph = new Paragraph() + .add(new Text(LocalStorage.ClinicName + "\n").setFont(boldFont).setFontSize(12)) // Reduced font + .add(new Text("Địa chỉ: " + LocalStorage.ClinicAddress + "\n").setFontSize(9)) // Reduced font + .add(new Text("Điện thoại: " + LocalStorage.ClinicPhone).setFontSize(9)) // Reduced font .setFont(font); - document.add(clinicInfoParagraph); + headerTable.addCell(new Cell().add(clinicInfoParagraph) + .setTextAlignment(com.itextpdf.layout.property.TextAlignment.LEFT) + .setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE) + .setBorder(Border.NO_BORDER).setPaddingLeft(5)); // Reduced padding + + document.add(headerTable); - // Add title - String title = "TOA THUỐC\n"; - Paragraph titleParagraph = new Paragraph(title) - .setFontSize(24).setFont(boldFont) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER); + // --- INVOICE TITLE --- + Paragraph titleParagraph = new Paragraph("TOA THUỐC") + .setFontSize(18).setFont(boldFont) // Reduced font size + .setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER) + .setMarginBottom(8); // Reduced margin document.add(titleParagraph); - // Create a table with 2 columns, each taking 50% of the width - Table patientDoctorTable = new Table(UnitValue.createPercentArray(new float[]{50, 50})) - .setWidth(UnitValue.createPercentValue(100)); + // --- PATIENT AND INVOICE DETAILS SECTION --- + Table patientDoctorTable = new Table(UnitValue.createPercentArray(new float[]{55, 45})) // Adjusted column widths + .setWidth(UnitValue.createPercentValue(100)) + .setFontSize(9).setFont(font) // Reduced font size + .setMarginBottom(8); // Reduced margin - // Add patient and doctor information in the first column (left-aligned) - Cell patientDoctorCell = new Cell() - .add(new Paragraph(String.format("Họ và tên: %s\nNgày sinh: %s\nSố điện thoại: %s\nĐịa chỉ: %s", - patientName, patientDOB, patientPhone, patientAddress))) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.LEFT) - .setBorder(Border.NO_BORDER) - .setFont(font); - patientDoctorTable.addCell(patientDoctorCell); + Cell patientInfoCell = new Cell().setBorder(Border.NO_BORDER).setPadding(3); // Reduced padding + Paragraph pName = new Paragraph().add(new Text("Họ và tên: ").setFont(boldFont)).add(patientName); + pName.setMultipliedLeading(1.0f); + patientInfoCell.add(pName); + Paragraph pDOB = new Paragraph().add(new Text("Ngày sinh: ").setFont(boldFont)).add(patientDOB); + pDOB.setMultipliedLeading(1.0f); + patientInfoCell.add(pDOB); + Paragraph pGender = new Paragraph().add(new Text("Giới tính: ").setFont(boldFont)).add(patientGender); + pGender.setMultipliedLeading(1.0f); + patientInfoCell.add(pGender); + Paragraph pPhone = new Paragraph().add(new Text("Số điện thoại: ").setFont(boldFont)).add(patientPhone); + pPhone.setMultipliedLeading(1.0f); + patientInfoCell.add(pPhone); + Paragraph pAddress = new Paragraph().add(new Text("Địa chỉ: ").setFont(boldFont)).add(patientAddress); + pAddress.setMultipliedLeading(1.0f); + patientInfoCell.add(pAddress); + patientDoctorTable.addCell(patientInfoCell); - // Add date and invoice information in the second column (right-aligned) - Cell moreInfoCell = new Cell() - .add(new Paragraph(String.format("Phái: %s\nBác sĩ: %s\nĐơn thuốc số: %s", - patientGender, doctorName, id))) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.LEFT) - .setBorder(Border.NO_BORDER) - .setFont(font); - patientDoctorTable.addCell(moreInfoCell); - - // Add the table to the document + Cell doctorInvoiceCell = new Cell().setBorder(Border.NO_BORDER).setPadding(3); // Reduced padding + Paragraph pDoctor = new Paragraph().add(new Text("Bác sĩ: ").setFont(boldFont)).add(doctorName); + pDoctor.setMultipliedLeading(1.0f); + doctorInvoiceCell.add(pDoctor); + Paragraph pDate = new Paragraph().add(new Text("Ngày lập: ").setFont(boldFont)).add(date); + pDate.setMultipliedLeading(1.0f); + doctorInvoiceCell.add(pDate); + Paragraph pInvoiceId = new Paragraph().add(new Text("Đơn thuốc số: ").setFont(boldFont)).add(id); + pInvoiceId.setMultipliedLeading(1.0f); + doctorInvoiceCell.add(pInvoiceId); + Paragraph pSpace1 = new Paragraph(" "); + pSpace1.setMultipliedLeading(1.0f); + doctorInvoiceCell.add(pSpace1); + Paragraph pSpace2 = new Paragraph(" "); + pSpace2.setMultipliedLeading(1.0f); + doctorInvoiceCell.add(pSpace2); + patientDoctorTable.addCell(doctorInvoiceCell); + document.add(patientDoctorTable); - // Diagnosis and notes - String diagnosisNotes = String.format("Chẩn đoán: %s\nGhi chú: %s\n", diagnosis, notes); - Paragraph diagnosisNotesParagraph = new Paragraph(diagnosisNotes).setFont(font); - document.add(diagnosisNotesParagraph); + // --- DIAGNOSIS AND NOTES --- + Paragraph diagnosisParagraph = new Paragraph(); + diagnosisParagraph.add(new Text("Chẩn đoán: ").setFont(boldFont)).add(diagnosis); + diagnosisParagraph.setFont(font).setFontSize(9).setMarginBottom(2).setMultipliedLeading(1.0f); + document.add(diagnosisParagraph); - - // Add services and medicine table - Table table = new Table(UnitValue.createPercentArray(new float[]{4, 2, 2})) - .setFont(font) - .setWidth(UnitValue.createPercentValue(100)) - .setBorder(Border.NO_BORDER); - table.addHeaderCell("Service/Medicine"); - table.addHeaderCell("Quantity"); - table.addHeaderCell("Price"); - long total = 0; - for(int i = 0; i < med.length; i++) { - table.addCell(med[i][1]); - table.addCell(med[i][4]); - table.addCell(med[i][6]); - total += Long.parseLong(med[i][4]) * Long.parseLong(med[i][6]); + if (notes != null && !notes.trim().isEmpty()) { + Paragraph notesParagraph = new Paragraph(); + notesParagraph.add(new Text("Ghi chú (chung): ").setFont(boldFont)).add(notes); + notesParagraph.setFont(font).setFontSize(9).setMarginBottom(5).setMultipliedLeading(1.0f); + document.add(notesParagraph); + } else { + document.add(new Paragraph().setMarginBottom(5)); } - // Merge the last row for total - Cell totalCell = new Cell(1, 2) - .add(new Paragraph("Tổng cộng")) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT); - table.addCell(totalCell); - table.addCell(new Cell().add(new Paragraph(String.valueOf(total))) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.LEFT)); - document.add(table); + // --- MEDICINE TABLE --- + Paragraph medicineSectionTitle = new Paragraph("CHI TIẾT THUỐC") + .setFont(boldFont).setFontSize(11) // Reduced font size + .setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER) + .setMarginTop(3).setMarginBottom(3); // Reduced margins + document.add(medicineSectionTitle); + + Table medTable = new Table(UnitValue.createPercentArray(new float[]{3.8f, 1.7f, 1.5f, 1.5f, 2.5f})) // Adjusted column widths + .setWidth(UnitValue.createPercentValue(100)) + .setFontSize(8).setFont(font) // Reduced font size for table content + .setMarginBottom(5); // Reduced margin - // Add signature area - Paragraph signatureArea = new Paragraph("\n\n\nChữ Ký: ________________________") - .setFont(font) - .setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT); - document.add(signatureArea); + medTable.addHeaderCell(new Cell().add(new Paragraph("Tên thuốc")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + medTable.addHeaderCell(new Cell().add(new Paragraph("Liều dùng")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + medTable.addHeaderCell(new Cell().add(new Paragraph("Số lượng")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + medTable.addHeaderCell(new Cell().add(new Paragraph("Đơn giá (VNĐ)")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + medTable.addHeaderCell(new Cell().add(new Paragraph("Thành tiền (VNĐ)")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + + long medicineTotal = 0; + if (med != null) { + for (String[] medicine : med) { + if (medicine == null || medicine.length < 10) { + medTable.addCell(new Cell(1, 5).add(new Paragraph("Thông tin thuốc không đầy đủ").setItalic().setFontSize(8)).setPadding(2)); + continue; + } + Paragraph medNameAndNote = new Paragraph(); + medNameAndNote.add(new Text(medicine[1]).setFontSize(8)); + if (medicine[9] != null && !medicine[9].isEmpty()) { + medNameAndNote.add(new Text("\nGhi chú: " + medicine[9]).setFontSize(7).setItalic()); + } + medNameAndNote.setMultipliedLeading(1.0f); + medTable.addCell(new Cell().add(medNameAndNote).setPadding(2)); + + String dosageInfo = String.format("S: %s, T: %s, C: %s", medicine[4], medicine[5], medicine[6]); + medTable.addCell(new Cell().add(new Paragraph(dosageInfo).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + medTable.addCell(new Cell().add(new Paragraph(medicine[2] + " " + medicine[3]).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + try { + medTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(Long.parseLong(medicine[7]))).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + medTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(Long.parseLong(medicine[8]))).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + medicineTotal += Long.parseLong(medicine[8]); + } catch (NumberFormatException e) { + System.err.println("Error parsing medicine price/total: " + medicine[7] + " or " + medicine[8]); + medTable.addCell(new Cell().add(new Paragraph("Lỗi giá")).setFontSize(8).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + medTable.addCell(new Cell().add(new Paragraph("Lỗi giá")).setFontSize(8).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + } + } + } + document.add(medTable); + + Table medTotalTable = new Table(UnitValue.createPercentArray(new float[]{7.5f, 2.5f})) + .setWidth(UnitValue.createPercentValue(100)).setBorder(Border.NO_BORDER).setMarginBottom(3); + medTotalTable.addCell(new Cell().add(new Paragraph("Tổng tiền thuốc:")).setFont(boldFont).setFontSize(9).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + medTotalTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(medicineTotal) + " VNĐ")).setFont(boldFont).setFontSize(9).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + document.add(medTotalTable); + + long overallTotal = medicineTotal; + + // --- SERVICES TABLE --- + if (services != null && services.length > 0) { + Paragraph serviceSectionTitle = new Paragraph("CHI TIẾT DỊCH VỤ") + .setFont(boldFont).setFontSize(11) // Reduced font size + .setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER) + .setMarginTop(5).setMarginBottom(3); // Reduced margins + document.add(serviceSectionTitle); + + Table serviceTable = new Table(UnitValue.createPercentArray(new float[]{4.5f, 1.5f, 2f, 2.5f})) // Adjusted column widths + .setWidth(UnitValue.createPercentValue(100)) + .setFontSize(8).setFont(font) // Reduced font size for table content + .setMarginBottom(5); // Reduced margin + + serviceTable.addHeaderCell(new Cell().add(new Paragraph("Tên dịch vụ")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + serviceTable.addHeaderCell(new Cell().add(new Paragraph("Số lượng")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + serviceTable.addHeaderCell(new Cell().add(new Paragraph("Đơn giá (VNĐ)")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + serviceTable.addHeaderCell(new Cell().add(new Paragraph("Thành tiền (VNĐ)")).setFont(boldFont).setFontSize(9).setBackgroundColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2)); + + long serviceTotal = 0; + for (String[] service : services) { + if (service == null || service.length < 5) { + serviceTable.addCell(new Cell(1, 4).add(new Paragraph("Thông tin dịch vụ không đầy đủ").setItalic().setFontSize(8)).setPadding(2)); + continue; + } + Paragraph serNameAndNote = new Paragraph(); + serNameAndNote.add(new Text(service[1]).setFontSize(8)); + if (service.length > 5 && service[5] != null && !service[5].isEmpty()) { + serNameAndNote.add(new Text("\nGhi chú: " + service[5]).setFontSize(7).setItalic()); + } + serNameAndNote.setMultipliedLeading(1.0f); + serviceTable.addCell(new Cell().add(serNameAndNote).setPadding(2)); + serviceTable.addCell(new Cell().add(new Paragraph(service[2]).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + try { + serviceTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(Long.parseLong(service[3]))).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + serviceTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(Long.parseLong(service[4]))).setFontSize(8)).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + serviceTotal += Long.parseLong(service[4]); + } catch (NumberFormatException e) { + System.err.println("Error parsing service price/total: " + service[3] + " or " + service[4]); + serviceTable.addCell(new Cell().add(new Paragraph("Lỗi giá")).setFontSize(8).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + serviceTable.addCell(new Cell().add(new Paragraph("Lỗi giá")).setFontSize(8).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setPadding(2).setVerticalAlignment(com.itextpdf.layout.property.VerticalAlignment.MIDDLE)); + } + } + document.add(serviceTable); + overallTotal += serviceTotal; + + Table serTotalTable = new Table(UnitValue.createPercentArray(new float[]{7.5f, 2.5f})) + .setWidth(UnitValue.createPercentValue(100)).setBorder(Border.NO_BORDER).setMarginBottom(3); + serTotalTable.addCell(new Cell().add(new Paragraph("Tổng tiền dịch vụ:")).setFont(boldFont).setFontSize(9).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + serTotalTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(serviceTotal) + " VNĐ")).setFont(boldFont).setFontSize(9).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + document.add(serTotalTable); + } + + // --- OVERALL TOTAL --- + document.add(new LineSeparator(new com.itextpdf.kernel.pdf.canvas.draw.SolidLine(0.5f)).setMarginTop(5).setMarginBottom(3)); + Table overallTotalTable = new Table(UnitValue.createPercentArray(new float[]{7.5f, 2.5f})) + .setWidth(UnitValue.createPercentValue(100)).setBorder(Border.NO_BORDER).setMarginBottom(10); + overallTotalTable.addCell(new Cell().add(new Paragraph("TỔNG CỘNG THANH TOÁN:")).setFont(boldFont).setFontSize(10).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + overallTotalTable.addCell(new Cell().add(new Paragraph(vndFormatter.format(overallTotal) + " VNĐ")).setFont(boldFont).setFontSize(10).setTextAlignment(com.itextpdf.layout.property.TextAlignment.RIGHT).setBorder(Border.NO_BORDER).setPadding(1)); + document.add(overallTotalTable); + + // --- SIGNATURE AREA --- + Table signatureTable = new Table(UnitValue.createPercentArray(new float[]{50, 50})) + .setWidth(UnitValue.createPercentValue(100)) + .setBorder(Border.NO_BORDER) + .setMarginTop(15); // Reduced margin + + signatureTable.addCell(new Cell().setBorder(Border.NO_BORDER)); + + Paragraph signatureParagraph = new Paragraph(); // Initialize paragraph + signatureParagraph.add(new Text("Ngày " + date.substring(0,2) + " tháng " + date.substring(3,5) + " năm " + date.substring(6) + "\n").setFontSize(9)); + signatureParagraph.add(new Text("Bác sĩ điều trị\n\n\n").setFontSize(9)); // Reduced newlines for space + signatureParagraph.add(new Text("(Ký, ghi rõ họ tên)").setFontSize(8).setItalic()); + signatureParagraph.setFont(font) + .setTextAlignment(com.itextpdf.layout.property.TextAlignment.CENTER) + .setMultipliedLeading(1.0f); // Applied to the Paragraph + + signatureTable.addCell(new Cell().add(signatureParagraph).setBorder(Border.NO_BORDER)); + document.add(signatureTable); document.close(); } @@ -288,6 +445,12 @@ public class MedicineInvoice{ // Put the panel inside a JScrollPane to make it scrollable JScrollPane scrollPane = new JScrollPane(pdfPanel); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); // Added for horizontal scroll if needed + + // Increase scroll sensitivity + JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); + verticalScrollBar.setUnitIncrement(20); // Increase for finer control, adjust as needed + verticalScrollBar.setBlockIncrement(100); // Increase for page-like scrolling, adjust as needed // Assuming `dialog` is the parent component dialog.add(scrollPane, BorderLayout.CENTER); diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/PrintDialog.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/PrintDialog.java deleted file mode 100644 index c2671a0..0000000 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/PrintDialog.java +++ /dev/null @@ -1,6 +0,0 @@ -package BsK.client.ui.component.CheckUpPage.PrintDialog; - - -public class PrintDialog { - -} diff --git a/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java b/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java new file mode 100644 index 0000000..4d1aab5 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/CheckoutPage/CheckoutPage.java @@ -0,0 +1,489 @@ +package BsK.client.ui.component.CheckoutPage; + +import BsK.client.LocalStorage; +import BsK.client.ui.component.MainFrame; +import BsK.client.ui.component.CheckoutPage.StandaloneMedicineDialog; +import BsK.client.ui.component.common.RoundedPanel; +import lombok.extern.slf4j.Slf4j; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Slf4j +public class CheckoutPage extends JPanel { + private JLabel activeNavItem = null; + private MainFrame mainFrame; + + // Left Panel Components + private JTable finishedCheckupTable; + private DefaultTableModel finishedCheckupTableModel; + private JButton newStandaloneBillButton; + + // Right Panel Components (Bill Details) + private JLabel billIdLabel; + private JLabel billDateLabel; + private JTextField customerNameField; // For standalone bills + private JTextField customerPhoneField; // For standalone bills + private JTextField barcodeField; + private JButton scanBarcodeButton; + private JButton addMedicineItemButton; + private JButton removeMedicineItemButton; + private JTable billItemsTable; + private DefaultTableModel billItemsTableModel; + private JLabel subtotalAmountLabel; + private JTextField discountField; + private JLabel grandTotalAmountLabel; + private JButton processPaymentButton; + private JButton printBillButton; + + private StandaloneMedicineDialog standaloneMedicineDialog; + + + public CheckoutPage(MainFrame mainFrame) { + this.mainFrame = mainFrame; + setLayout(new BorderLayout()); + setBackground(new Color(240, 240, 240)); + + // --- Navigation Bar --- + JPanel navBar = new JPanel(); + navBar.setBackground(new Color(63, 81, 181)); + navBar.setLayout(new BoxLayout(navBar, BoxLayout.X_AXIS)); + + JPanel navItemsPanel = new JPanel(); + navItemsPanel.setBackground(new Color(63, 81, 181)); + navItemsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 15)); + navItemsPanel.setAlignmentY(Component.CENTER_ALIGNMENT); + + String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Kho", "Thanh toán", "Người dùng", "Thông tin"}; + String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "InventoryPage", "CheckoutPage", "UserPage", "InfoPage"}; + String[] iconFiles = {"dashboard.png", "health-check.png", "database.png", "warehouse.png", "cashier-machine.png", "user.png", "info.png"}; + + final Color defaultNavColor = new Color(63, 81, 181); + final Color hoverNavColor = new Color(50, 70, 170); + final Color activeNavColor = new Color(33, 150, 243); + + final Border defaultNavItemBorder = BorderFactory.createEmptyBorder(12, 15, 12, 15); + final Border activeCheckoutSpecificBorder = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(0, 0, 3, 0, activeNavColor), + BorderFactory.createEmptyBorder(12, 15, 9, 15) + ); + + for (int i = 0; i < navBarItems.length; i++) { + final String itemText = navBarItems[i]; + final String dest = destination[i]; + String iconFileName = iconFiles[i]; + final JLabel label = new JLabel(itemText); + // ... (rest of nav item setup - copied from DashboardPage/CheckUpPage) + label.setForeground(Color.WHITE); + label.setHorizontalAlignment(SwingConstants.CENTER); + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + label.setFont(new Font("Arial", Font.BOLD, 12)); + label.setBorder(defaultNavItemBorder); + label.setOpaque(false); + + try { + String iconPath = "src/main/java/BsK/client/ui/assets/icon/" + iconFileName; + ImageIcon originalIcon = new ImageIcon(iconPath); + Image scaledImage = originalIcon.getImage().getScaledInstance(36, 36, Image.SCALE_SMOOTH); + ImageIcon scaledIcon = new ImageIcon(scaledImage); + label.setIcon(scaledIcon); + } catch (Exception e) { + log.error("Error loading icon: {} for nav item: {}", iconFileName, itemText, e); + } + + label.setVerticalTextPosition(SwingConstants.BOTTOM); + label.setHorizontalTextPosition(SwingConstants.CENTER); + + if (itemText.equals("Thanh toán")) { // Highlight "Thanh toán" as active + label.setBorder(activeCheckoutSpecificBorder); + label.setBackground(activeNavColor); + label.setOpaque(true); + activeNavItem = label; + } + + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (activeNavItem != null && activeNavItem != label) { + activeNavItem.setBackground(defaultNavColor); + activeNavItem.setOpaque(false); + activeNavItem.setForeground(Color.WHITE); + activeNavItem.setBorder(defaultNavItemBorder); + } + activeNavItem = label; + activeNavItem.setBackground(activeNavColor); + activeNavItem.setOpaque(true); + activeNavItem.setForeground(Color.WHITE); + if (itemText.equals("Thanh toán")) { + activeNavItem.setBorder(activeCheckoutSpecificBorder); + } else { + activeNavItem.setBorder(defaultNavItemBorder); + } + mainFrame.showPage(dest); + } + @Override + public void mouseEntered(MouseEvent e) { if (label != activeNavItem) { label.setForeground(new Color(200, 230, 255)); label.setBackground(hoverNavColor);label.setOpaque(true);}} + @Override + public void mouseExited(MouseEvent e) { if (label != activeNavItem) { label.setForeground(Color.WHITE); label.setBackground(defaultNavColor); label.setOpaque(false);}} + }); + navItemsPanel.add(label); + } + + navBar.add(navItemsPanel); + navBar.add(Box.createHorizontalGlue()); + JLabel welcomeLabel = new JLabel("Chào, " + LocalStorage.username + " "); + // ... (rest of welcomeLabel setup - copied from DashboardPage/CheckUpPage) + welcomeLabel.setForeground(Color.WHITE); + welcomeLabel.setHorizontalAlignment(SwingConstants.CENTER); + welcomeLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + welcomeLabel.setFont(new Font("Arial", Font.BOLD, 14)); + welcomeLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e)) { + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem profileItem = new JMenuItem("Hồ sơ cá nhân"); + JMenuItem settingsItem = new JMenuItem("Cài đặt"); + JMenuItem logoutItem = new JMenuItem("Đăng xuất"); + + profileItem.addActionListener(event -> { + JOptionPane.showMessageDialog(mainFrame, "Tính năng Hồ sơ cá nhân sắp ra mắt!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + }); + settingsItem.addActionListener(event -> { + JOptionPane.showMessageDialog(mainFrame, "Tính năng Cài đặt sắp ra mắt!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + }); + logoutItem.addActionListener(event -> { + // TODO: Implement actual logout with NetworkUtil.sendPacket if ClientHandler is available + // NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new LogoutRequest()); + LocalStorage.username = null; + LocalStorage.userId = -1; + mainFrame.showPage("LandingPage"); + }); + popupMenu.add(profileItem); + popupMenu.add(settingsItem); + popupMenu.addSeparator(); + popupMenu.add(logoutItem); + popupMenu.setPreferredSize(new Dimension(150, popupMenu.getPreferredSize().height)); + popupMenu.show(welcomeLabel, 0, welcomeLabel.getHeight()); + } + } + @Override + public void mouseEntered(MouseEvent e) { welcomeLabel.setForeground(new Color(200, 230, 255)); } + @Override + public void mouseExited(MouseEvent e) { welcomeLabel.setForeground(Color.WHITE); } + }); + navBar.add(welcomeLabel); + navBar.setPreferredSize(new Dimension(1200, 85)); + add(navBar, BorderLayout.NORTH); + + // --- Main Content Area --- + JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + mainSplitPane.setResizeWeight(0.35); // Give more space to bill details initially + mainSplitPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + // --- Left Panel: Queues and New Bill --- + JPanel leftPanel = new JPanel(new BorderLayout(10, 10)); + leftPanel.setBorder(BorderFactory.createEmptyBorder(10,0,0,0)); + + // Finished Checkup Queue Panel + RoundedPanel finishedQueuePanel = new RoundedPanel(15, Color.WHITE, false); + finishedQueuePanel.setLayout(new BorderLayout()); + finishedQueuePanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(63, 81, 181), 1, true), + "Chờ thanh toán (đã khám)", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 16), new Color(63, 81, 181) + )); + + String[] finishedColumns = {"Mã Bill", "Tên BN", "Tổng tiền", "Trạng thái"}; + finishedCheckupTableModel = new DefaultTableModel(new Object[][]{}, finishedColumns) { + public boolean isCellEditable(int row, int column) { return false; } + }; + finishedCheckupTable = new JTable(finishedCheckupTableModel); + finishedCheckupTable.setRowHeight(25); + finishedCheckupTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); + finishedCheckupTable.setFont(new Font("Arial", Font.PLAIN, 12)); + JScrollPane finishedTableScroll = new JScrollPane(finishedCheckupTable); + finishedQueuePanel.add(finishedTableScroll, BorderLayout.CENTER); + // TODO: Populate this table with patients who have Status.COMPLETED_CHECKUP or similar + // TODO: Add MouseListener to finishedCheckupTable to load bill details on selection + + leftPanel.add(finishedQueuePanel, BorderLayout.CENTER); + + newStandaloneBillButton = new JButton("Tạo hóa đơn mới (khách lẻ)"); + newStandaloneBillButton.setFont(new Font("Arial", Font.BOLD, 14)); + newStandaloneBillButton.setBackground(new Color(0, 150, 136)); + newStandaloneBillButton.setForeground(Color.WHITE); + newStandaloneBillButton.setFocusPainted(false); + newStandaloneBillButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + newStandaloneBillButton.setBorder(BorderFactory.createEmptyBorder(10,15,10,15)); + newStandaloneBillButton.addActionListener(e -> createNewStandaloneBill()); + JPanel newBillButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + newBillButtonPanel.setOpaque(false); + newBillButtonPanel.add(newStandaloneBillButton); + leftPanel.add(newBillButtonPanel, BorderLayout.SOUTH); + + mainSplitPane.setLeftComponent(leftPanel); + + // --- Right Panel: Bill Details --- + RoundedPanel rightPanel = new RoundedPanel(15, Color.WHITE, false); + rightPanel.setLayout(new BorderLayout(10,10)); + rightPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 10, 0, 0), + BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(63, 81, 181), 1, true), + "Chi tiết hóa đơn", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 16), new Color(63, 81, 181) + ) + )); + + JPanel billInfoAndActionsPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(5,5,5,5); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.WEST; + + // Bill Info (ID, Date, Customer for standalone) + gbc.gridx = 0; gbc.gridy = 0; billInfoAndActionsPanel.add(new JLabel("Mã HĐ:"), gbc); + gbc.gridx = 1; billIdLabel = new JLabel("N/A"); billInfoAndActionsPanel.add(billIdLabel, gbc); + gbc.gridx = 2; billInfoAndActionsPanel.add(new JLabel("Ngày:"), gbc); + gbc.gridx = 3; billDateLabel = new JLabel(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); billInfoAndActionsPanel.add(billDateLabel, gbc); + + gbc.gridx = 0; gbc.gridy = 1; billInfoAndActionsPanel.add(new JLabel("Tên KH (lẻ):"), gbc); + gbc.gridx = 1; gbc.gridwidth=3; customerNameField = new JTextField(20); billInfoAndActionsPanel.add(customerNameField, gbc); gbc.gridwidth=1; + gbc.gridx = 0; gbc.gridy = 2; billInfoAndActionsPanel.add(new JLabel("SĐT KH (lẻ):"), gbc); + gbc.gridx = 1; gbc.gridwidth=3; customerPhoneField = new JTextField(15); billInfoAndActionsPanel.add(customerPhoneField, gbc); gbc.gridwidth=1; + + // Barcode Scan + gbc.gridx = 0; gbc.gridy = 3; billInfoAndActionsPanel.add(new JLabel("Barcode:"), gbc); + gbc.gridx = 1; gbc.gridwidth=2; barcodeField = new JTextField(20); billInfoAndActionsPanel.add(barcodeField, gbc); + gbc.gridx = 3; gbc.gridwidth=1; scanBarcodeButton = new JButton("Quét"); billInfoAndActionsPanel.add(scanBarcodeButton, gbc); + scanBarcodeButton.addActionListener(e -> scanBarcodeAction()); + gbc.gridwidth=1; + + // Bill Items Table + String[] billItemCols = {"Mã Thuốc", "Tên thuốc", "SL", "ĐVT", "Đơn giá", "Thành tiền"}; + billItemsTableModel = new DefaultTableModel(new Object[][]{}, billItemCols) { + public boolean isCellEditable(int row, int column) { return false; } // Or allow SL edit + }; + billItemsTable = new JTable(billItemsTableModel); + billItemsTable.setRowHeight(25); + billItemsTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); + billItemsTable.setFont(new Font("Arial", Font.PLAIN, 12)); + JScrollPane billItemsScroll = new JScrollPane(billItemsTable); + + gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 4; gbc.weightx = 1.0; gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + billInfoAndActionsPanel.add(billItemsScroll, gbc); + gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weighty = 0.0; + + // Add/Remove Medicine Buttons for Bill Items + JPanel itemActionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + addMedicineItemButton = new JButton("Thêm thuốc vào HĐ"); + addMedicineItemButton.addActionListener(e -> openStandaloneMedicineDialog()); + removeMedicineItemButton = new JButton("Xóa thuốc khỏi HĐ"); + removeMedicineItemButton.addActionListener(e -> removeMedicineFromBillAction()); + itemActionPanel.add(addMedicineItemButton); + itemActionPanel.add(removeMedicineItemButton); + gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 4; billInfoAndActionsPanel.add(itemActionPanel, gbc); + + + // Totals Panel + JPanel totalsPanel = new JPanel(new GridBagLayout()); + GridBagConstraints totalGbc = new GridBagConstraints(); + totalGbc.insets = new Insets(3,10,3,10); + totalGbc.anchor = GridBagConstraints.EAST; + + totalGbc.gridx=0; totalGbc.gridy=0; totalsPanel.add(new JLabel("Tổng phụ:"), totalGbc); + totalGbc.gridx=1; subtotalAmountLabel = new JLabel("0 VNĐ"); totalsPanel.add(subtotalAmountLabel, totalGbc); + totalGbc.gridx=0; totalGbc.gridy++; totalsPanel.add(new JLabel("Giảm giá:"), totalGbc); + totalGbc.gridx=1; discountField = new JTextField("0", 8); discountField.setHorizontalAlignment(JTextField.RIGHT); totalsPanel.add(discountField, totalGbc); + // TODO: Add listener to discountField to update grand total + totalGbc.gridx=0; totalGbc.gridy++; totalsPanel.add(new JLabel("TỔNG CỘNG:"), totalGbc); + totalGbc.gridx=1; grandTotalAmountLabel = new JLabel("0 VNĐ"); + grandTotalAmountLabel.setFont(new Font("Arial", Font.BOLD, 16)); + grandTotalAmountLabel.setForeground(Color.RED); + totalsPanel.add(grandTotalAmountLabel, totalGbc); + + gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.EAST; + billInfoAndActionsPanel.add(totalsPanel, gbc); + + + // Main Action Buttons (Payment, Print) + JPanel mainActionsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); + processPaymentButton = new JButton("Thanh Toán"); + processPaymentButton.setFont(new Font("Arial", Font.BOLD, 16)); + processPaymentButton.setBackground(new Color(76, 175, 80)); + processPaymentButton.setForeground(Color.WHITE); + processPaymentButton.addActionListener(e -> processPaymentAction()); + + printBillButton = new JButton("In Hóa Đơn"); + printBillButton.setFont(new Font("Arial", Font.BOLD, 16)); + printBillButton.setBackground(new Color(33, 150, 243)); + printBillButton.setForeground(Color.WHITE); + printBillButton.addActionListener(e -> printBillAction()); + + mainActionsPanel.add(processPaymentButton); + mainActionsPanel.add(printBillButton); + + rightPanel.add(billInfoAndActionsPanel, BorderLayout.CENTER); + rightPanel.add(mainActionsPanel, BorderLayout.SOUTH); + + mainSplitPane.setRightComponent(new JScrollPane(rightPanel)); // Make right panel scrollable too + + add(mainSplitPane, BorderLayout.CENTER); + + // TODO: Load initial finished checkup queue + loadFinishedCheckupQueue(); + updateBillDetailsPanel(null); // Initially no bill selected or new bill + } + + private void loadFinishedCheckupQueue() { + // TODO: Network request to get patients with status like "ĐÃ KHÁM XONG" or "CHỜ THANH TOÁN" + // For now, add dummy data + finishedCheckupTableModel.addRow(new Object[]{"CK001", "Nguyễn Văn A", "250,000 VNĐ", "Chờ TT"}); + finishedCheckupTableModel.addRow(new Object[]{"CK002", "Trần Thị B", "180,000 VNĐ", "Chờ TT"}); + log.info("Dummy finished checkup queue loaded."); + } + + private void createNewStandaloneBill() { + log.info("Creating new standalone bill."); + // TODO: Generate a new Bill ID (e.g., "BL" + timestamp or sequence) + billIdLabel.setText("BL" + System.currentTimeMillis()%10000); + billDateLabel.setText(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); + customerNameField.setText(""); + customerPhoneField.setText(""); + customerNameField.setEditable(true); + customerPhoneField.setEditable(true); + barcodeField.setText(""); + billItemsTableModel.setRowCount(0); // Clear items table + subtotalAmountLabel.setText("0 VNĐ"); + discountField.setText("0"); + grandTotalAmountLabel.setText("0 VNĐ"); + // TODO: Clear/reset any other relevant fields for a new bill + // TODO: Focus on customer name or barcode field + } + + private void updateBillDetailsPanel(/* BillObject bill/Patient patient */ Object selectedBillData) { + if (selectedBillData == null) { // For new bill or initial state + billIdLabel.setText("N/A"); + billDateLabel.setText(new SimpleDateFormat("dd/MM/yyyy").format(new Date())); + customerNameField.setText(""); customerNameField.setEditable(true); + customerPhoneField.setText(""); customerPhoneField.setEditable(true); + barcodeField.setText(""); + billItemsTableModel.setRowCount(0); + updateTotals(); + } else { + // TODO: Populate right panel based on selectedBillData (from finished queue) + // Example: + // billIdLabel.setText(bill.getId()); + // billDateLabel.setText(bill.getDate()); + // customerNameField.setText(patient.getName()); customerNameField.setEditable(false); + // customerPhoneField.setText(patient.getPhone()); customerPhoneField.setEditable(false); + // billItemsTableModel.setDataVector(bill.getItems(), billItemCols); + // updateTotals(); + log.info("Loading bill details for: " + selectedBillData.toString()); + } + } + + + private void scanBarcodeAction() { + String barcode = barcodeField.getText().trim(); + if (barcode.isEmpty()) { + JOptionPane.showMessageDialog(this, "Vui lòng nhập barcode.", "Barcode trống", JOptionPane.WARNING_MESSAGE); + return; + } + // TODO: Network request to find medicine by barcode + // If found, add to billItemsTable or open dialog with prefilled info + log.info("Barcode scanned (mock): " + barcode); + JOptionPane.showMessageDialog(this, "Đã quét barcode (mock): " + barcode + "\nTODO: Tìm thuốc và thêm vào HĐ.", "Quét Barcode", JOptionPane.INFORMATION_MESSAGE); + } + + private void openStandaloneMedicineDialog() { + if (standaloneMedicineDialog == null) { + standaloneMedicineDialog = new StandaloneMedicineDialog(mainFrame); + } + standaloneMedicineDialog.setVisible(true); + String[][] selectedMeds = standaloneMedicineDialog.getMedicinePrescription(); + if (selectedMeds != null) { + for (String[] med : selectedMeds) { + // med: ID, Tên thuốc, SL, ĐVT, Sáng, Trưa, Chiều, Đơn giá, Thành tiền, Ghi chú + // For bill, we might only need: ID, Tên thuốc, SL, ĐVT, Đơn giá, Thành tiền + if (med.length >= 9) { + billItemsTableModel.addRow(new Object[]{ + med[0], // ID + med[1], // Tên thuốc + med[2], // SL + med[3], // ĐVT + med[7], // Đơn giá + med[8] // Thành tiền + }); + } + } + updateTotals(); + } + } + + private void removeMedicineFromBillAction() { + int selectedRow = billItemsTable.getSelectedRow(); + if (selectedRow != -1) { + billItemsTableModel.removeRow(selectedRow); + updateTotals(); + } else { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc để xóa khỏi hóa đơn.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); + } + } + + private void updateTotals() { + double subtotal = 0; + for (int i = 0; i < billItemsTableModel.getRowCount(); i++) { + try { + // Assuming "Thành tiền" is at column 5 and is a string like "120,000" + String totalStr = billItemsTableModel.getValueAt(i, 5).toString().replace(",", ""); + subtotal += Double.parseDouble(totalStr); + } catch (Exception e) { + log.error("Error calculating subtotal for row " + i, e); + } + } + subtotalAmountLabel.setText(String.format("%,.0f VNĐ", subtotal)); + + double discount = 0; + try { + discount = Double.parseDouble(discountField.getText().replace(",", "")); + } catch (NumberFormatException e) { + // ignore, keep discount 0 + } + + double grandTotal = subtotal - discount; + grandTotalAmountLabel.setText(String.format("%,.0f VNĐ", grandTotal)); + } + + private void processPaymentAction() { + // TODO: Implement payment processing logic + // - Validate bill (items, total > 0) + // - Send request to backend to mark bill as paid, update inventory + // - Show success/failure message + // - Potentially clear the bill or offer to print + String billId = billIdLabel.getText(); + String total = grandTotalAmountLabel.getText(); + log.info("Processing payment for Bill ID: " + billId + ", Amount: " + total); + JOptionPane.showMessageDialog(this, "THANH TOÁN THÀNH CÔNG (mock)\nBill ID: "+billId+"\nTổng tiền: "+total+"\nTODO: Lưu HĐ, cập nhật kho...", "Xác nhận thanh toán", JOptionPane.INFORMATION_MESSAGE); + createNewStandaloneBill(); // Clear for next bill + } + + private void printBillAction() { + // TODO: Implement bill printing + // - Gather all necessary bill data (customer, items, totals) + // - Create a new Invoice/Bill printing class (similar to MedicineInvoice but for general bills) + // - Generate and show/print the PDF + log.info("Printing bill (mock) for Bill ID: " + billIdLabel.getText()); + JOptionPane.showMessageDialog(this, "Đang chuẩn bị in hóa đơn (mock)...\nTODO: Tạo file PDF hóa đơn.", "In Hóa Đơn", JOptionPane.INFORMATION_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java b/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java new file mode 100644 index 0000000..71f07c5 --- /dev/null +++ b/src/main/java/BsK/client/ui/component/CheckoutPage/StandaloneMedicineDialog.java @@ -0,0 +1,646 @@ +package BsK.client.ui.component.CheckoutPage; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumnModel; + +import BsK.client.network.handler.ClientHandler; +import BsK.client.network.handler.ResponseListener; +import BsK.common.packet.req.GetMedInfoRequest; +import BsK.common.packet.res.GetMedInfoResponse; +import BsK.common.util.network.NetworkUtil; +import BsK.common.util.text.TextUtils; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Slf4j +public class StandaloneMedicineDialog extends JDialog { + private JTextField medicineNameField; + private JTextField barcodeField; // New field for barcode + private JTextField medicineCompanyField; + private JTextArea medicineDescriptionField; + private JSpinner quantitySpinner; + private JTextField quantityLeftField; + private JComboBox UnitComboBox; + private JTextField priceField; + private JTextField totalField; + private JSpinner morningSpinner, noonSpinner, eveningSpinner; + private JTextArea noteField; + private DefaultTableModel tableModel, selectedTableModel; + private String[] medcineColumns = {"ID", "Tên thuốc", "Barcode", "Công ty", "Mô tả", "Tồn kho", "ĐVT", "Giá"}; // Added Barcode Column + private String[][] medicineData; + private final ResponseListener getMedInfoResponseListener = this::getMedInfoHandler; + private TableColumnModel columnModel; + private HashMap selectedMedicine = new HashMap<>(); + private JTable medicineTable; + private JTable selectedTable; + private boolean isProgrammaticallySettingMedicineNameField = false; + + private String[][] medicinePrescription; + private static final Logger logger = LoggerFactory.getLogger(StandaloneMedicineDialog.class); + + public String[][] getMedicinePrescription() { + return medicinePrescription; + } + + void getMedInfoHandler(GetMedInfoResponse response) { + logger.info("Received medicine data for Standalone Dialog"); + medicineData = response.getMedInfo(); // Assuming GetMedInfoResponse also returns barcode in medInfo array + tableModel.setDataVector(medicineData, medcineColumns); + resizeMedicineTableColumns(); + } + + private void sendGetMedInfoRequest() { + logger.info("Sending GetMedInfoRequest for Standalone Dialog"); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetMedInfoRequest()); + } + + public StandaloneMedicineDialog(Frame parent) { + super(parent, "Thêm thuốc vào hóa đơn (khách lẻ)", true); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setSize(1250, 800); + setLocationRelativeTo(parent); + setResizable(true); + + ClientHandler.addResponseListener(GetMedInfoResponse.class, getMedInfoResponseListener); + sendGetMedInfoRequest(); + + setLayout(new BorderLayout(10, 10)); + + 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; + + // Search Panel (Name and Barcode) + JPanel searchPanel = new JPanel(new GridBagLayout()); + searchPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Tìm kiếm thuốc", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) + )); + mainGbc.gridx = 0; mainGbc.gridy = 0; + mainInputPanel.add(searchPanel, mainGbc); + + JPanel medicineInfoPanel = new JPanel(new GridBagLayout()); + medicineInfoPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Thông tin thuốc", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) + )); + mainGbc.gridy = 1; + mainInputPanel.add(medicineInfoPanel, mainGbc); + + JPanel quantityPricePanel = new JPanel(new GridBagLayout()); + quantityPricePanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Số lượng & Giá", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) + )); + mainGbc.gridy = 2; + mainInputPanel.add(quantityPricePanel, mainGbc); + + JPanel dosagePanel = new JPanel(new GridBagLayout()); + dosagePanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Liều dùng (nếu có)", // Dosage might be optional for direct sale + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) + )); + mainGbc.gridy = 3; + mainInputPanel.add(dosagePanel, mainGbc); + + JPanel notePanel = new JPanel(new GridBagLayout()); + notePanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Ghi chú (nếu có)", + javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, + new Font("Arial", Font.BOLD, 14), new Color(50, 50, 50) + )); + mainGbc.gridy = 4; + mainInputPanel.add(notePanel, mainGbc); + + JPanel addMedicineButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JButton addMedicineButton = new JButton("Thêm thuốc vào HĐ"); + addMedicineButtonPanel.add(addMedicineButton); + mainGbc.gridy = 5; + mainInputPanel.add(addMedicineButtonPanel, mainGbc); + + mainGbc.gridy = 6; mainGbc.weighty = 1.0; + mainInputPanel.add(new JPanel(), mainGbc); // Filler panel + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(3, 5, 3, 5); // Reduced insets slightly + gbc.weightx = 0.0; gbc.weighty = 0.0; + + // Search Panel Content + gbc.gridy = 0; gbc.gridx = 0; + searchPanel.add(new JLabel("Tên thuốc:"), gbc); + gbc.gridx = 1; gbc.weightx = 1.0; + medicineNameField = new JTextField(20); + searchPanel.add(medicineNameField, gbc); + gbc.weightx = 0.0; + gbc.gridx = 2; gbc.insets = new Insets(3, 15, 3, 5); // More left inset for barcode label + searchPanel.add(new JLabel("Barcode:"), gbc); + gbc.insets = new Insets(3, 5, 3, 5); + gbc.gridx = 3; gbc.weightx = 1.0; + barcodeField = new JTextField(15); + searchPanel.add(barcodeField, gbc); + gbc.weightx = 0.0; + + // Medicine Info Panel Content + gbc.gridy = 0; gbc.gridx = 0; + medicineInfoPanel.add(new JLabel("Công ty:"), gbc); + gbc.gridx = 1; gbc.weightx = 1.0; + medicineCompanyField = new JTextField(20); + medicineCompanyField.setEditable(false); + medicineInfoPanel.add(medicineCompanyField, gbc); + gbc.weightx = 0.0; + + gbc.gridy++; gbc.gridx = 0; + gbc.anchor = GridBagConstraints.NORTHWEST; + medicineInfoPanel.add(new JLabel("Mô tả:"), gbc); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx = 1; gbc.weightx = 1.0; + medicineDescriptionField = new JTextArea(2, 20); // Reduced rows + medicineDescriptionField.setLineWrap(true); + medicineDescriptionField.setWrapStyleWord(true); + medicineDescriptionField.setEditable(false); + JScrollPane descriptionScrollPane = new JScrollPane(medicineDescriptionField); + descriptionScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + medicineInfoPanel.add(descriptionScrollPane, gbc); + gbc.weightx = 0.0; + + // Quantity & Price Panel Content (similar to MedicineDialog) + gbc.gridy = 0; gbc.gridx = 0; + quantityPricePanel.add(new JLabel("Số lượng:"), gbc); + gbc.gridx = 1; gbc.weightx = 0.5; + quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 1000, 1)); // Increased max quantity + quantitySpinner.setPreferredSize(new Dimension(80, 25)); + quantityPricePanel.add(quantitySpinner, gbc); + gbc.gridx = 2; gbc.weightx = 0.0; + quantityPricePanel.add(new JLabel("Còn lại:"), gbc); + gbc.gridx = 3; gbc.weightx = 0.5; + quantityLeftField = new JTextField(5); + quantityLeftField.setEditable(false); + quantityPricePanel.add(quantityLeftField, gbc); + gbc.weightx = 0.0; + gbc.gridy++; gbc.gridx = 0; + quantityPricePanel.add(new JLabel("ĐVT:"), gbc); + gbc.gridx = 1; gbc.weightx = 0.5; + String[] units = {"Viên", "Vỉ", "Hộp", "Chai", "Tuýp", "Gói"}; + UnitComboBox = new JComboBox<>(units); + UnitComboBox.setEnabled(false); + quantityPricePanel.add(UnitComboBox, gbc); + gbc.gridx = 2; gbc.weightx = 0.0; + quantityPricePanel.add(new JLabel("Giá (VNĐ):"), gbc); + gbc.gridx = 3; gbc.weightx = 0.5; + priceField = new JTextField(10); + priceField.setEditable(false); + quantityPricePanel.add(priceField, gbc); + gbc.weightx = 0.0; + gbc.gridy++; gbc.gridx = 0; + quantityPricePanel.add(new JLabel("Thành tiền (VNĐ):"), gbc); + gbc.gridx = 1; gbc.gridwidth = 3; gbc.weightx = 1.0; + totalField = new JTextField(10); + totalField.setEditable(false); + quantityPricePanel.add(totalField, gbc); + gbc.gridwidth = 1; gbc.weightx = 0.0; + + // Dosage Panel Content + gbc.gridy = 0; gbc.gridx = 0; gbc.weightx = 0.0; + dosagePanel.add(new JLabel("Sáng:"), gbc); + gbc.gridx = 1; gbc.weightx = 0.3; + morningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); + morningSpinner.setPreferredSize(new Dimension(60,25)); + dosagePanel.add(morningSpinner, gbc); + gbc.gridx = 2; gbc.weightx = 0.0; + dosagePanel.add(new JLabel("Trưa:"), gbc); + gbc.gridx = 3; gbc.weightx = 0.3; + noonSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); + noonSpinner.setPreferredSize(new Dimension(60,25)); + dosagePanel.add(noonSpinner, gbc); + gbc.gridx = 4; gbc.weightx = 0.0; + dosagePanel.add(new JLabel("Chiều:"), gbc); + gbc.gridx = 5; gbc.weightx = 0.3; + eveningSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1)); + eveningSpinner.setPreferredSize(new Dimension(60,25)); + dosagePanel.add(eveningSpinner, gbc); + gbc.weightx = 0.0; + + // Note Panel Content + gbc.gridy = 0; gbc.gridx = 0; + notePanel.add(new JLabel("Ghi chú:"), gbc); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx = 1; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.weighty = 1.0; + noteField = new JTextArea(2, 20); // Reduced rows + noteField.setLineWrap(true); + noteField.setWrapStyleWord(true); + JScrollPane noteScrollPane = new JScrollPane(noteField); + noteScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + notePanel.add(noteScrollPane, gbc); + gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.0; gbc.weighty = 0.0; + + // Medicine Table (Right Side) + tableModel = new DefaultTableModel(medicineData, medcineColumns) { + @Override public boolean isCellEditable(int row, int column) { return false; } + }; + medicineTable = new JTable(tableModel); + medicineTable.setFont(new Font("Arial", Font.PLAIN, 13)); + medicineTable.setRowHeight(22); + medicineTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + medicineTable.setAutoCreateRowSorter(true); + JScrollPane medicineTableScrollPane = new JScrollPane(medicineTable); + medicineTableScrollPane.setPreferredSize(new Dimension(650, 250)); // Wider table + + // Selected Medicine Table (Right Side, Bottom) + String[] selectedColumnNames = {"ID", "Tên thuốc", "SL", "ĐVT", "Sáng", "Trưa", "Chiều", "Đơn giá", "Thành tiền", "Ghi chú"}; + selectedTableModel = new DefaultTableModel(new String[][]{}, selectedColumnNames) { + @Override public boolean isCellEditable(int row, int column) { + // Allow editing for SL, Dosage, Note in selected table + return column == 2 || column == 4 || column == 5 || column == 6 || column == 9; + } + }; + selectedTable = new JTable(selectedTableModel); + selectedTable.setFont(new Font("Arial", Font.PLAIN, 13)); + selectedTable.setRowHeight(22); + JScrollPane selectedTableScrollPane = new JScrollPane(selectedTable); + selectedTableScrollPane.setPreferredSize(new Dimension(650, 180)); // Wider table + + JPanel removeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton removeMedicineButton = new JButton("Xóa thuốc đã chọn"); + removeButtonPanel.add(removeMedicineButton); + + JPanel rightPanel = new JPanel(new BorderLayout(0, 5)); + rightPanel.add(medicineTableScrollPane, BorderLayout.CENTER); + JPanel bottomRightsPanel = new JPanel(new BorderLayout(0,5)); + bottomRightsPanel.add(selectedTableScrollPane, BorderLayout.CENTER); + bottomRightsPanel.add(removeButtonPanel, BorderLayout.SOUTH); + rightPanel.add(bottomRightsPanel, BorderLayout.SOUTH); + + JScrollPane mainInputScrollPane = new JScrollPane(mainInputPanel); + mainInputScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + mainInputScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + mainInputScrollPane.setBorder(BorderFactory.createEmptyBorder()); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mainInputScrollPane, rightPanel); + splitPane.setResizeWeight(0.42); // Adjusted resize weight slightly + add(splitPane, BorderLayout.CENTER); + + JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton okButton = new JButton("Lưu & Đóng"); + JButton cancelButton = new JButton("Hủy"); + bottomButtonPanel.add(okButton); + bottomButtonPanel.add(cancelButton); + add(bottomButtonPanel, BorderLayout.SOUTH); + + // Event Listeners + medicineNameField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); } + public void removeUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); } + public void insertUpdate(DocumentEvent e) { if (!isProgrammaticallySettingMedicineNameField) SwingUtilities.invokeLater(() -> filterMedicineTableByName()); } + }); + + barcodeField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + filterMedicineTableByBarcode(); + } + } + }); + + medicineTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 1) { + SwingUtilities.invokeLater(() -> { + int selectedRow = medicineTable.getSelectedRow(); + if (selectedRow != -1) handleMedicineTableRowSelection(selectedRow); + }); + } + } + }); + + medicineNameField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleTableNavigationKeys(e, medicineTable); + } + }); + barcodeField.addKeyListener(new KeyAdapter() { // Allow table nav from barcode field too + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + filterMedicineTableByBarcode(); + } else { + handleTableNavigationKeys(e, medicineTable); + } + } + }); + + quantitySpinner.addChangeListener(e -> updateTotalField()); + UnitComboBox.addActionListener(e -> updateTotalField()); // Though disabled, good practice + + addMedicineButton.addActionListener(e -> addSelectedMedicine()); + removeMedicineButton.addActionListener(e -> removeSelectedMedicine()); + + okButton.addActionListener(e -> { + collectPrescriptionData(); + if (medicinePrescription == null || medicinePrescription.length == 0) { + // For standalone sale, it's okay to have no prescription if just closing + // But if they intend to add items, perhaps a different message or behavior + } + logger.info("Standalone prescription/items saved."); + // Log for debugging + for (String[] row : medicinePrescription) { + logger.info("Med: ID={}, Name={}, Qty={}, Unit={}, M={}, N={}, E={}, Price={}, Total={}, Note={}", + (Object[]) row); + } + dispose(); + }); + + cancelButton.addActionListener(e -> { + medicinePrescription = null; // Clear any collected data if cancelled + dispose(); + }); + } + + private void handleTableNavigationKeys(KeyEvent e, JTable targetTable) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (targetTable.getSelectedRow() != -1) addSelectedMedicine(); + } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { + if (targetTable.getRowCount() > 0) { + int selectedRow = targetTable.getSelectedRow(); + selectedRow = (selectedRow < targetTable.getRowCount() - 1) ? selectedRow + 1 : 0; + targetTable.setRowSelectionInterval(selectedRow, selectedRow); + targetTable.scrollRectToVisible(targetTable.getCellRect(selectedRow, 0, true)); + handleMedicineTableRowSelection(selectedRow); + } + } else if (e.getKeyCode() == KeyEvent.VK_UP) { + if (targetTable.getRowCount() > 0) { + int selectedRow = targetTable.getSelectedRow(); + selectedRow = (selectedRow > 0) ? selectedRow - 1 : targetTable.getRowCount() - 1; + targetTable.setRowSelectionInterval(selectedRow, selectedRow); + targetTable.scrollRectToVisible(targetTable.getCellRect(selectedRow, 0, true)); + handleMedicineTableRowSelection(selectedRow); + } + } + } + + private void filterMedicineTableByName() { + filterMedicineTable(medicineNameField.getText().trim(), null); + } + + private void filterMedicineTableByBarcode() { + filterMedicineTable(null, barcodeField.getText().trim()); + if (medicineTable.getRowCount() == 1) { // If unique match by barcode + medicineTable.setRowSelectionInterval(0,0); + handleMedicineTableRowSelection(0); + addSelectedMedicine(); // Optionally auto-add if unique and quantity is set + barcodeField.setText(""); // Clear barcode field after processing + medicineNameField.requestFocusInWindow(); // Or quantitySpinner + } + } + + private void filterMedicineTable(String nameFilter, String barcodeFilter) { + if (medicineData == null) return; + + boolean nameIsEmpty = (nameFilter == null || nameFilter.isEmpty()); + boolean barcodeIsEmpty = (barcodeFilter == null || barcodeFilter.isEmpty()); + + if (nameIsEmpty && barcodeIsEmpty) { + tableModel.setDataVector(medicineData, medcineColumns); + resizeMedicineTableColumns(); + medicineTable.clearSelection(); + clearInputFields(); // Also clear input fields when filter is cleared + } else { + List filteredData = new ArrayList<>(); + String lowerCaseNameFilter = nameIsEmpty ? "" : TextUtils.removeAccents(nameFilter.toLowerCase()); + // Barcode is usually exact match or startsWith, not contains typically + String lowerCaseBarcodeFilter = barcodeIsEmpty ? "" : barcodeFilter.toLowerCase(); + + for (String[] row : medicineData) { + boolean nameMatch = nameIsEmpty; // True if no name filter + boolean barcodeMatch = barcodeIsEmpty; // True if no barcode filter + + if (!nameIsEmpty && row.length > 1 && row[1] != null) { + nameMatch = TextUtils.removeAccents(row[1].toLowerCase()).contains(lowerCaseNameFilter); + } + // Assuming barcode is at index 2 in medicineData from server (ID, Name, Barcode, Company...) + if (!barcodeIsEmpty && row.length > 2 && row[2] != null) { + barcodeMatch = row[2].toLowerCase().equals(lowerCaseBarcodeFilter); // Exact match for barcode + } + + if (nameMatch && barcodeMatch) { // If name filter provided, must match name. If barcode provided, must match barcode. + filteredData.add(row); + } + } + tableModel.setDataVector(filteredData.toArray(new String[0][0]), medcineColumns); + resizeMedicineTableColumns(); + if (!filteredData.isEmpty()) { + medicineTable.setRowSelectionInterval(0,0); + handleMedicineTableRowSelection(0); + } else { + clearInputFields(); + } + } + } + + private void resizeMedicineTableColumns() { + if (medicineTable.getColumnCount() == 0) return; + columnModel = medicineTable.getColumnModel(); + // ID, Tên thuốc, Barcode, Công ty, Mô tả, Tồn kho, ĐVT, Giá + columnModel.getColumn(0).setPreferredWidth(30); // ID + columnModel.getColumn(1).setPreferredWidth(150); // Tên thuốc + columnModel.getColumn(2).setPreferredWidth(100); // Barcode + columnModel.getColumn(3).setPreferredWidth(100); // Công ty + columnModel.getColumn(4).setPreferredWidth(180); // Mô tả + columnModel.getColumn(5).setPreferredWidth(50); // Tồn kho + columnModel.getColumn(6).setPreferredWidth(40); // ĐVT + columnModel.getColumn(7).setPreferredWidth(60); // Giá + } + + private void handleMedicineTableRowSelection(int viewRow) { + if (viewRow < 0 || viewRow >= medicineTable.getRowCount()) { + clearInputFields(); + return; + } + int modelRow = medicineTable.convertRowIndexToModel(viewRow); + + isProgrammaticallySettingMedicineNameField = true; + medicineNameField.setText(tableModel.getValueAt(modelRow, 1).toString()); + isProgrammaticallySettingMedicineNameField = false; + // Assuming barcode is column 2. If not present in tableModel directly, might need to fetch from medicineData using ID + if (tableModel.getColumnCount() > 2 && tableModel.getValueAt(modelRow, 2) != null) { + barcodeField.setText(tableModel.getValueAt(modelRow, 2).toString()); + } + medicineCompanyField.setText(tableModel.getValueAt(modelRow, 3).toString()); + medicineDescriptionField.setText(tableModel.getValueAt(modelRow, 4).toString()); + quantityLeftField.setText(tableModel.getValueAt(modelRow, 5).toString()); + UnitComboBox.setSelectedItem(tableModel.getValueAt(modelRow, 6).toString()); + priceField.setText(tableModel.getValueAt(modelRow, 7).toString()); + + quantitySpinner.setValue(1); + morningSpinner.setValue(0); // Reset dosage for standalone sale, or make it optional + noonSpinner.setValue(0); + eveningSpinner.setValue(0); + noteField.setText(""); + updateTotalField(); + } + + private void clearInputFields() { + // Don't clear medicineNameField or barcodeField as they are used for filtering + medicineCompanyField.setText(""); + medicineDescriptionField.setText(""); + quantityLeftField.setText(""); + UnitComboBox.setSelectedIndex(0); // Or set to a default/blank if applicable + priceField.setText(""); + totalField.setText(""); + quantitySpinner.setValue(1); + morningSpinner.setValue(0); + noonSpinner.setValue(0); + eveningSpinner.setValue(0); + noteField.setText(""); + } + + private void updateTotalField() { + try { + int quantity = (Integer) quantitySpinner.getValue(); + if (quantity < 0) { quantitySpinner.setValue(0); quantity = 0; } + double price = priceField.getText().isEmpty() ? 0 : Double.parseDouble(priceField.getText().replace(",","")); + totalField.setText(String.format("%,.0f", quantity * price)); // Format with commas + } catch (NumberFormatException e) { + totalField.setText("Giá trị không hợp lệ"); + logger.error("Error parsing price for total calculation: " + priceField.getText(), e); + } + } + + private void addSelectedMedicine() { + int selectedRowInMedicineTable = medicineTable.getSelectedRow(); + String medicineId, name, unit, currentPrice, barcodeVal; + + if (selectedRowInMedicineTable != -1) { + int modelRow = medicineTable.convertRowIndexToModel(selectedRowInMedicineTable); + medicineId = tableModel.getValueAt(modelRow, 0).toString(); + name = tableModel.getValueAt(modelRow, 1).toString(); + barcodeVal = tableModel.getColumnCount() > 2 && tableModel.getValueAt(modelRow, 2) != null ? tableModel.getValueAt(modelRow, 2).toString() : "N/A"; + unit = tableModel.getValueAt(modelRow, 6).toString(); // ĐVT is at 6 (0-indexed) + currentPrice = tableModel.getValueAt(modelRow, 7).toString(); // Giá is at 7 + } else if (!medicineNameField.getText().isEmpty() || !barcodeField.getText().isEmpty()){ + // Attempt to find by name or barcode if no table selection but fields have text + String searchName = medicineNameField.getText().trim(); + String searchBarcode = barcodeField.getText().trim(); + String foundId = null, foundUnit = null, foundPrice = null, actualName = null, actualBarcode = null; + + for (String[] med : medicineData) { // medicineData: ID, Tên thuốc, Barcode, Công ty, Mô tả, Tồn kho, ĐVT, Giá + boolean nameMatches = !searchName.isEmpty() && TextUtils.removeAccents(med[1].toLowerCase()).contains(TextUtils.removeAccents(searchName.toLowerCase())); + boolean barcodeMatches = !searchBarcode.isEmpty() && med.length > 2 && med[2] != null && med[2].equalsIgnoreCase(searchBarcode); + if (nameMatches || barcodeMatches) { + foundId = med[0]; actualName = med[1]; actualBarcode = med[2]; + foundUnit = med[6]; foundPrice = med[7]; + break; // Take first match + } + } + if (foundId == null) { + JOptionPane.showMessageDialog(this, "Không tìm thấy thuốc khớp với tên hoặc barcode đã nhập.", "Không tìm thấy thuốc", JOptionPane.WARNING_MESSAGE); + return; + } + medicineId = foundId; name = actualName; unit = foundUnit; currentPrice = foundPrice; barcodeVal = actualBarcode; + } else { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ danh sách hoặc nhập tên/barcode.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); + return; + } + + int quantity = (Integer) quantitySpinner.getValue(); + int morningVal = (Integer) morningSpinner.getValue(); + int noonVal = (Integer) noonSpinner.getValue(); + int eveningVal = (Integer) eveningSpinner.getValue(); + String note = noteField.getText(); + String totalAmount = totalField.getText().replace(",",""); // Remove comma for internal storage if needed + + if (quantity <= 0) { + JOptionPane.showMessageDialog(this, "Số lượng phải lớn hơn 0.", "Số lượng không hợp lệ", JOptionPane.WARNING_MESSAGE); + return; + } + selectedTableModel.addRow(new Object[]{medicineId, name, quantity, unit, morningVal, noonVal, eveningVal, currentPrice, totalAmount, note}); + } + + private void removeSelectedMedicine() { + int selectedRow = selectedTable.getSelectedRow(); + if (selectedRow != -1) { + selectedTableModel.removeRow(selectedRow); + } else { + JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ bảng dưới để xóa.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); + } + } + + private void collectPrescriptionData() { + int rowCount = selectedTableModel.getRowCount(); + if (rowCount == 0) { + medicinePrescription = new String[0][0]; + return; + } + medicinePrescription = new String[rowCount][10]; + for (int i = 0; i < rowCount; i++) { + for (int j = 0; j < 10; j++) { + medicinePrescription[i][j] = selectedTableModel.getValueAt(i, j) != null ? selectedTableModel.getValueAt(i, j).toString() : ""; + } + } + } + + @Override + public void dispose() { + logger.info("Cleaning up StandaloneMedicineDialog listeners"); + ClientHandler.deleteListener(GetMedInfoResponse.class); // Ensure this matches the listener added + super.dispose(); + } + + // Main method for testing this dialog independently + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Test Standalone Medicine Dialog"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(600, 400); + frame.setLocationRelativeTo(null); + JButton openDialogButton = new JButton("Open Standalone Medicine Dialog"); + openDialogButton.addActionListener(e -> { + StandaloneMedicineDialog dialog = new StandaloneMedicineDialog(frame); + dialog.setVisible(true); + String[][] prescription = dialog.getMedicinePrescription(); + if (prescription != null && prescription.length > 0) { + System.out.println("Standalone Items obtained:"); + for (String[] item : prescription) { + // Ensure correct number of items for format string + if (item.length == 10) { + System.out.println(String.format("ID:%s, Name:%s, Qty:%s, Unit:%s, M:%s, N:%s, E:%s, Price:%s, Total:%s, Note:%s", + (Object[]) item)); + } else { + System.out.println("Item with incorrect number of fields: " + java.util.Arrays.toString(item)); + } + } + } else { + System.out.println("No items or dialog was cancelled."); + } + }); + frame.setLayout(new FlowLayout()); + frame.add(openDialogButton); + frame.setVisible(true); + }); + } +} \ No newline at end of file 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 5dd3e40..bb1eecc 100644 --- a/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java +++ b/src/main/java/BsK/client/ui/component/DashboardPage/DashboardPage.java @@ -4,134 +4,219 @@ import BsK.client.LocalStorage; import BsK.client.ui.component.MainFrame; import BsK.client.ui.component.common.RoundedButtonUI; import BsK.client.ui.component.common.RoundedPanel; +import lombok.extern.slf4j.Slf4j; import javax.swing.*; +import javax.swing.border.Border; import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +@Slf4j public class DashboardPage extends JPanel { + private JLabel activeNavItem = null; // To keep track of the active navigation item + public DashboardPage(MainFrame mainFrame) { setLayout(new BorderLayout()); + setBackground(new Color(240, 240, 240)); // A light background for the page - // Sidebar panel - JPanel sidebar = new JPanel() { - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2d = (Graphics2D) g; - int width = getWidth(); - int height = getHeight(); - Color color1 = new Color(63, 81, 181); - Color color2 = new Color(33, 150, 243); - GradientPaint gp = new GradientPaint(0, 0, color1, 0, height, color2); - g2d.setPaint(gp); - g2d.fillRect(0, 0, width, height); - } - }; - sidebar.setBackground(new Color(63, 81, 181)); - sidebar.setLayout(new GridLayout(15, 1)); - sidebar.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + // --- Navigation Bar (copied and adapted from CheckUpPage) --- + JPanel navBar = new JPanel(); + navBar.setBackground(new Color(63, 81, 181)); + navBar.setLayout(new BoxLayout(navBar, BoxLayout.X_AXIS)); - String[] sidebarItems = {"Thống kê","Thăm khám", "Dữ liệu bệnh nhân", "Kho", "Người dùng", "Thông tin"}; - String[] destination = {"DashboardPage","CheckUpPage", "PatientDataPage", "InventoryPage", "UserPage", "InfoPage"}; - for(int i = 0; i < sidebarItems.length; i++) { - String item = sidebarItems[i]; - String dest = destination[i]; - JLabel label = new JLabel(item); + JPanel navItemsPanel = new JPanel(); + navItemsPanel.setBackground(new Color(63, 81, 181)); + navItemsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 15)); + navItemsPanel.setAlignmentY(Component.CENTER_ALIGNMENT); + + String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Kho", "Thanh toán", "Người dùng", "Thông tin"}; + String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "InventoryPage", "CheckoutPage", "UserPage", "InfoPage"}; + String[] iconFiles = {"dashboard.png", "health-check.png", "database.png", "warehouse.png", "cashier-machine.png", "user.png", "info.png"}; + + final Color defaultNavColor = new Color(63, 81, 181); + final Color hoverNavColor = new Color(50, 70, 170); + final Color activeNavColor = new Color(33, 150, 243); + + final Border defaultNavItemBorder = BorderFactory.createEmptyBorder(12, 15, 12, 15); + // Specific border for the active "Thống kê" page + final Border activeDashboardSpecificBorder = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(0, 0, 3, 0, activeNavColor), + BorderFactory.createEmptyBorder(12, 15, 9, 15) + ); + + for (int i = 0; i < navBarItems.length; i++) { + final String itemText = navBarItems[i]; + final String dest = destination[i]; + String iconFileName = iconFiles[i]; + + final JLabel label = new JLabel(itemText); label.setForeground(Color.WHITE); label.setHorizontalAlignment(SwingConstants.CENTER); - label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); // Change cursor to hand - label.setFont(new Font("Arial", Font.BOLD, 14)); + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + label.setFont(new Font("Arial", Font.BOLD, 12)); + label.setBorder(defaultNavItemBorder); + label.setOpaque(false); + + try { + String iconPath = "src/main/java/BsK/client/ui/assets/icon/" + iconFileName; + ImageIcon originalIcon = new ImageIcon(iconPath); + Image scaledImage = originalIcon.getImage().getScaledInstance(36, 36, Image.SCALE_SMOOTH); + ImageIcon scaledIcon = new ImageIcon(scaledImage); + label.setIcon(scaledIcon); + } catch (Exception e) { + log.error("Error loading icon: {} for nav item: {}", iconFileName, itemText, e); + } + + label.setVerticalTextPosition(SwingConstants.BOTTOM); + label.setHorizontalTextPosition(SwingConstants.CENTER); + + if (itemText.equals("Thống kê")) { // Highlight "Thống kê" as active + label.setBorder(activeDashboardSpecificBorder); + label.setBackground(activeNavColor); + label.setOpaque(true); + activeNavItem = label; + } - // Add a mouse listener to handle click events label.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mouseClicked(java.awt.event.MouseEvent e) { - // Handle the click event + if (activeNavItem != null && activeNavItem != label) { + activeNavItem.setBackground(defaultNavColor); + activeNavItem.setOpaque(false); + activeNavItem.setForeground(Color.WHITE); + activeNavItem.setBorder(defaultNavItemBorder); + } + activeNavItem = label; + activeNavItem.setBackground(activeNavColor); + activeNavItem.setOpaque(true); + activeNavItem.setForeground(Color.WHITE); + // Apply specific border only if it's the designated active page for this class + if (itemText.equals("Thống kê")) { + activeNavItem.setBorder(activeDashboardSpecificBorder); + } else { + activeNavItem.setBorder(defaultNavItemBorder); + } mainFrame.showPage(dest); - - // JOptionPane.showMessageDialog(null, item + " clicked!"); } @Override public void mouseEntered(java.awt.event.MouseEvent e) { - label.setForeground(new Color(200, 230, 255)); // Highlight on hover + if (label != activeNavItem) { + label.setForeground(new Color(200, 230, 255)); + label.setBackground(hoverNavColor); + label.setOpaque(true); + } } @Override public void mouseExited(java.awt.event.MouseEvent e) { - label.setForeground(Color.WHITE); // Restore original color + if (label != activeNavItem) { + label.setForeground(Color.WHITE); + label.setBackground(defaultNavColor); + label.setOpaque(false); + } } }); - - sidebar.add(label); + navItemsPanel.add(label); } -// Make the sidebar scrollable - JScrollPane sidebarScrollPane = new JScrollPane(sidebar); - sidebarScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); - sidebarScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - sidebarScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); - sidebarScrollPane.getViewport().setOpaque(false); - sidebarScrollPane.setOpaque(false); + navBar.add(navItemsPanel); + navBar.add(Box.createHorizontalGlue()); + JLabel welcomeLabel = new JLabel("Chào, " + LocalStorage.username + " "); + welcomeLabel.setForeground(Color.WHITE); + welcomeLabel.setHorizontalAlignment(SwingConstants.CENTER); + welcomeLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + welcomeLabel.setFont(new Font("Arial", Font.BOLD, 14)); + welcomeLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e)) { + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem profileItem = new JMenuItem("Hồ sơ cá nhân"); + JMenuItem settingsItem = new JMenuItem("Cài đặt"); + JMenuItem logoutItem = new JMenuItem("Đăng xuất"); + profileItem.addActionListener(event -> { + JOptionPane.showMessageDialog(mainFrame, "Tính năng Hồ sơ cá nhân sắp ra mắt!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + }); + settingsItem.addActionListener(event -> { + JOptionPane.showMessageDialog(mainFrame, "Tính năng Cài đặt sắp ra mắt!", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + }); + // For Logout, you'll need ClientHandler and LogoutRequest from CheckUpPage context if not already available + // For now, just a placeholder action + logoutItem.addActionListener(event -> { + // NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new LogoutRequest()); // Requires imports + LocalStorage.username = null; + LocalStorage.userId = -1; + mainFrame.showPage("LandingPage"); + }); + popupMenu.add(profileItem); + popupMenu.add(settingsItem); + popupMenu.addSeparator(); + popupMenu.add(logoutItem); + popupMenu.setPreferredSize(new Dimension(150, popupMenu.getPreferredSize().height)); + popupMenu.show(welcomeLabel, 0, welcomeLabel.getHeight()); + } + } + @Override + public void mouseEntered(MouseEvent e) { + welcomeLabel.setForeground(new Color(200, 230, 255)); + } + @Override + public void mouseExited(MouseEvent e) { + welcomeLabel.setForeground(Color.WHITE); + } + }); + navBar.add(welcomeLabel); + navBar.setPreferredSize(new Dimension(1200, 85)); - // Topbar panel - JPanel topbar = new JPanel(); - topbar.setLayout(new BorderLayout()); - topbar.setBackground(Color.WHITE); - topbar.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + add(navBar, BorderLayout.NORTH); - JLabel title = new JLabel("Dashboard / Home"); - title.setFont(new Font("Arial", Font.BOLD, 18)); - topbar.add(title, BorderLayout.WEST); - - JLabel userInfo = new JLabel("Welcome, " + LocalStorage.username); - userInfo.setHorizontalAlignment(SwingConstants.RIGHT); - topbar.add(userInfo, BorderLayout.EAST); - - // Main content panel + // --- Main Content Panel (from existing DashboardPage) --- JPanel mainContent = new JPanel(); mainContent.setLayout(new BorderLayout()); mainContent.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + mainContent.setOpaque(false); // Make it transparent to see page background // Top cards JPanel topCards = new JPanel(); + topCards.setOpaque(false); // Make transparent topCards.setLayout(new GridLayout(1, 4, 20, 20)); String[] cardTitles = {"Add Check", "Income", "Expense", "Other Income"}; - Color[] cardColors = {Color.PINK, Color.CYAN, Color.ORANGE, Color.GREEN}; + // Using slightly more professional/subtle colors + Color[] cardColors = {new Color(100, 181, 246), new Color(77, 208, 225), new Color(255, 183, 77), new Color(129, 199, 132)}; for (int i = 0; i < cardTitles.length; i++) { - JPanel card = new RoundedPanel(20, cardColors[i], true); - card.setBackground(cardColors[i]); - card.setLayout(new BorderLayout()); - card.setBorder(BorderFactory.createEmptyBorder(30, 20, 30, 20)); + RoundedPanel card = new RoundedPanel(15, cardColors[i], true); // Slightly smaller radius + card.setBackground(cardColors[i]); // Ensure background is set + card.setLayout(new BorderLayout(10,10)); + card.setBorder(BorderFactory.createEmptyBorder(20, 15, 20, 15)); // Adjusted padding - JLabel cardTitle = new JLabel(cardTitles[i]); - cardTitle.setForeground(Color.WHITE); - cardTitle.setHorizontalAlignment(SwingConstants.CENTER); - card.add(cardTitle, BorderLayout.NORTH); + JLabel cardTitleLabel = new JLabel(cardTitles[i]); + cardTitleLabel.setForeground(Color.WHITE); + cardTitleLabel.setFont(new Font("Arial", Font.BOLD, 16)); // Larger title + cardTitleLabel.setHorizontalAlignment(SwingConstants.CENTER); + card.add(cardTitleLabel, BorderLayout.NORTH); - JLabel cardValue = new JLabel("Value", SwingConstants.CENTER); - cardValue.setForeground(Color.WHITE); - cardValue.setFont(new Font("Arial", Font.BOLD, 16)); - card.add(cardValue, BorderLayout.CENTER); + JLabel cardValueLabel = new JLabel("N/A"); // Placeholder value + cardValueLabel.setForeground(Color.WHITE); + cardValueLabel.setFont(new Font("Arial", Font.PLAIN, 24)); // Larger value text + cardValueLabel.setHorizontalAlignment(SwingConstants.CENTER); + card.add(cardValueLabel, BorderLayout.CENTER); topCards.add(card); } - mainContent.add(topCards, BorderLayout.NORTH); - // Wrapper panel for spacing - JPanel wrapperPanel = new JPanel(); - wrapperPanel.setLayout(new BorderLayout()); - wrapperPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0)); // Add spacing above - wrapperPanel.setOpaque(false); // Ensure transparency for background compatibility + JPanel wrapperPanel = new JPanel(new BorderLayout()); + wrapperPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0)); + wrapperPanel.setOpaque(false); -// Data table inside a RoundedPanel - RoundedPanel dataTable = new RoundedPanel(20, Color.WHITE, false); - dataTable.setLayout(new BorderLayout()); - dataTable.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); // Adjust padding for better appearance + RoundedPanel dataTablePanel = new RoundedPanel(15, Color.WHITE, false); + dataTablePanel.setLayout(new BorderLayout()); + dataTablePanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); String[] columns = {"Name", "Gender", "Course", "Fees", "Action"}; String[][] data = { @@ -140,19 +225,18 @@ public class DashboardPage extends JPanel { {"Bora", "Male", "C#", "$300", "Edit/Delete"}, }; JTable table = new JTable(data, columns); + table.setRowHeight(25); + table.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14)); + table.setFont(new Font("Arial", Font.PLAIN, 12)); JScrollPane tableScroll = new JScrollPane(table); - tableScroll.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Inner padding - dataTable.add(tableScroll, BorderLayout.CENTER); + tableScroll.setBorder(BorderFactory.createEmptyBorder(10,0,0,0)); // Top padding for header + dataTablePanel.add(tableScroll, BorderLayout.CENTER); -// Add RoundedPanel to the wrapper panel - wrapperPanel.add(dataTable, BorderLayout.CENTER); - -// Add the wrapper panel to the main content + wrapperPanel.add(dataTablePanel, BorderLayout.CENTER); mainContent.add(wrapperPanel, BorderLayout.CENTER); - - - // Notice board + // Notice board (keeping it commented as in original) + /* JPanel noticeBoard = new RoundedPanel(20, Color.WHITE, false); noticeBoard.setLayout(new BorderLayout()); noticeBoard.setBorder(BorderFactory.createTitledBorder("Notice Board")); @@ -161,21 +245,19 @@ public class DashboardPage extends JPanel { JTextArea noticeText = new JTextArea("04/10/2021\nHidemode Now\n\n03/10/2021\nFurther Reading\n"); noticeText.setEditable(false); noticeBoard.add(new JScrollPane(noticeText), BorderLayout.CENTER); + mainContent.add(noticeBoard, BorderLayout.EAST); + */ - //mainContent.add(noticeBoard, BorderLayout.EAST); - - // Adding panels to the frame - add(sidebarScrollPane, BorderLayout.WEST); - add(topbar, BorderLayout.NORTH); add(mainContent, BorderLayout.CENTER); } - + // Main method (usually for testing, can be kept or removed) public static void main(String[] args) { // JFrame frame = new JFrame("Dashboard"); // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); -// frame.setSize(800, 600); -// frame.add(new DashboardPage()); +// frame.setSize(1200, 800); +// DashboardPage d = new DashboardPage(null); // Pass null or a mock MainFrame for testing +// frame.add(d); // frame.setVisible(true); } } \ No newline at end of file