mirror of
https://github.com/tiennm99/BSK-All-in-One-Clinic-Management-System.git
synced 2026-10-14 16:16:50 +00:00
[add] Update medical invoice format and enhance CheckUpPage UI with improved navigation and service details. Add checkout page
This commit is contained in:
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:
|
||||
|
||||
Whitespace-only changes.
+239
-76
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user