[add] Update medical invoice format and enhance CheckUpPage UI with improved navigation and service details. Add checkout page

This commit is contained in:
Lê Thành Đạt committed 2025-05-30 01:01:01 +07:00
1 parent 8e95a9e6ce
commit d6fc00f232
14 files changed
+1631 -205

No files matched your search

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -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<String> 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:
@@ -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);
@@ -1,6 +0,0 @@
package BsK.client.ui.component.CheckUpPage.PrintDialog;
public class PrintDialog {
}
@@ -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);
}
}
@@ -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<String> 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<GetMedInfoResponse> getMedInfoResponseListener = this::getMedInfoHandler;
private TableColumnModel columnModel;
private HashMap<String, Boolean> 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<String[]> 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);
});
}
}
@@ -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);
}
}