[update] Introduce new medical unit conversion mapping in LocalStorage. Enhance AddDialog with improved UI for province and ward selection, including additional labels for better user guidance. Remove CheckoutPage and InventoryPage components to streamline the application structure.

This commit is contained in:
Lê Thành Đạt committed 2025-07-27 01:07:43 +07:00
1 parent a08a7de3c5
commit 45a0a2d792
16 files changed
+599 -5201

No files matched your search

@@ -20,4 +20,5 @@ public class LocalStorage {
public static String[] wards;
public static HashMap<String, String> provinceToId;
public static HashMap<String, String[]> cachedWards; // cache local areas later will do
public static HashMap<String, String[][]> medUnitConversion; // hash by med id inside is unit id and unit name and conversion rate and unit price
}
@@ -484,12 +484,28 @@ public class AddDialog extends JDialog {
gbc.gridx = 0; // Start from the first column for the combo boxes
gbc.gridwidth = 1; // Each combo box takes 1 logical column in this setup
gbc.weightx = 0.33; // Distribute space among the three combo boxes
// Add Tỉnh/Thành phố label before provinceComboBox
gbc.gridx = 0;
JLabel provinceLabel = new JLabel("Tỉnh/Thành phố", SwingConstants.RIGHT);
provinceLabel.setFont(labelFont);
addressInfoPanel.add(provinceLabel, gbc);
gbc.gridx = 1;
provinceComboBox = new JComboBox<>(LocalStorage.provinces != null ? LocalStorage.provinces : new String[]{"Tỉnh/TP"});
provinceComboBox.setFont(textFont);
provinceComboBox.setPreferredSize(comboBoxSize);
addressInfoPanel.add(provinceComboBox, gbc);
gbc.gridx = 1;
gbc.gridx = 2;
gbc.gridwidth = 1;
gbc.weightx = 0.33;
JLabel wardLabel = new JLabel("Xã/Phường", SwingConstants.RIGHT);
wardLabel.setFont(labelFont);
addressInfoPanel.add(wardLabel, gbc);
gbc.gridx = 3;
gbc.gridwidth = 1;
gbc.weightx = 0.33;
wardModel = new DefaultComboBoxModel<>(new String[]{"Xã/Phường"});
wardComboBox = new JComboBox<>(wardModel);
wardComboBox.setFont(textFont);
File diff suppressed because it is too large. Load diff
@@ -9,108 +9,444 @@ 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.awt.event.ActionListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
public class DashboardPage extends JPanel {
private final MainFrame mainFrame;
// TODO: Add backend request handlers
private JLabel todayPatientsLabel;
private JLabel recheckPatientsLabel;
private JLabel totalPatientsLabel;
private JLabel pendingResultsLabel;
private JTable recentActivitiesTable;
private JLabel lastUpdateLabel;
public DashboardPage(MainFrame mainFrame) {
this.mainFrame = mainFrame;
setLayout(new BorderLayout());
setBackground(new Color(240, 240, 240)); // A light background for the page
setBackground(new Color(245, 247, 250));
// --- Navigation Bar ---
NavBar navBar = new NavBar(mainFrame, "Thống kê");
add(navBar, BorderLayout.NORTH);
// --- Main Content Panel (from existing DashboardPage) ---
// --- Main Content Panel ---
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
mainContent.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainContent.setOpaque(false);
// 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"};
// 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)};
// Create main sections
JPanel topSection = createTopSection();
JPanel centerSection = createCenterSection();
JPanel bottomSection = createBottomSection();
for (int i = 0; i < cardTitles.length; i++) {
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 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 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);
JPanel wrapperPanel = new JPanel(new BorderLayout());
wrapperPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
wrapperPanel.setOpaque(false);
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 = {
{"John", "Male", "Java", "$300", "Edit/Delete"},
{"Dara", "Male", "C++", "$300", "Edit/Delete"},
{"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,0,0,0)); // Top padding for header
dataTablePanel.add(tableScroll, BorderLayout.CENTER);
wrapperPanel.add(dataTablePanel, BorderLayout.CENTER);
mainContent.add(wrapperPanel, BorderLayout.CENTER);
// 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"));
noticeBoard.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
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(topSection, BorderLayout.NORTH);
mainContent.add(centerSection, BorderLayout.CENTER);
mainContent.add(bottomSection, BorderLayout.SOUTH);
add(mainContent, BorderLayout.CENTER);
// TODO: Initialize with real data from backend
loadDashboardData();
}
private JPanel createTopSection() {
JPanel topSection = new JPanel(new BorderLayout());
topSection.setOpaque(false);
topSection.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
// Welcome header
JPanel headerPanel = createHeaderPanel();
// Patient metrics cards
JPanel metricsPanel = createMetricsPanel();
topSection.add(headerPanel, BorderLayout.NORTH);
topSection.add(metricsPanel, BorderLayout.CENTER);
return topSection;
}
private JPanel createHeaderPanel() {
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.setOpaque(false);
headerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
JLabel welcomeLabel = new JLabel("Chào mừng, " + LocalStorage.username);
welcomeLabel.setFont(new Font("Segoe UI", Font.BOLD, 28));
welcomeLabel.setForeground(new Color(44, 82, 130));
lastUpdateLabel = new JLabel("Cập nhật lần cuối: " + getCurrentTime());
lastUpdateLabel.setFont(new Font("Segoe UI", Font.PLAIN, 14));
lastUpdateLabel.setForeground(new Color(107, 114, 128));
JButton refreshButton = new JButton("Làm mới");
refreshButton.setFont(new Font("Segoe UI", Font.BOLD, 12));
refreshButton.setBackground(new Color(59, 130, 246));
refreshButton.setForeground(Color.WHITE);
refreshButton.setFocusPainted(false);
refreshButton.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16));
refreshButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
refreshButton.addActionListener(e -> {
// TODO: Implement refresh data from backend
refreshDashboardData();
});
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setOpaque(false);
leftPanel.add(welcomeLabel, BorderLayout.NORTH);
leftPanel.add(lastUpdateLabel, BorderLayout.SOUTH);
headerPanel.add(leftPanel, BorderLayout.WEST);
headerPanel.add(refreshButton, BorderLayout.EAST);
return headerPanel;
}
private JPanel createMetricsPanel() {
JPanel metricsPanel = new JPanel(new GridLayout(1, 4, 20, 0));
metricsPanel.setOpaque(false);
// Patient metrics cards
String[] cardTitles = {"Bệnh nhân hôm nay", "Cần tái khám", "Tổng bệnh nhân", "Chờ kết quả"};
Color[] cardColors = {
new Color(59, 130, 246), // Blue - Today's patients
new Color(245, 158, 11), // Orange - Recheck patients
new Color(16, 185, 129), // Green - Total patients
new Color(139, 92, 246) // Purple - Pending results
};
String[] cardIcons = {"👥", "🔄", "📊", "⏳"};
JLabel[] valueLabels = new JLabel[4];
for (int i = 0; i < cardTitles.length; i++) {
RoundedPanel card = createMetricCard(cardTitles[i], cardColors[i], cardIcons[i]);
// Store references to value labels for updating
Component[] components = card.getComponents();
for (Component comp : components) {
if (comp instanceof JPanel) {
Component[] subComps = ((JPanel) comp).getComponents();
for (Component subComp : subComps) {
if (subComp instanceof JLabel && ((JLabel) subComp).getFont().getSize() == 32) {
valueLabels[i] = (JLabel) subComp;
break;
}
}
}
}
metricsPanel.add(card);
}
// Store references for updating data
todayPatientsLabel = valueLabels[0];
recheckPatientsLabel = valueLabels[1];
totalPatientsLabel = valueLabels[2];
pendingResultsLabel = valueLabels[3];
return metricsPanel;
}
private RoundedPanel createMetricCard(String title, Color color, String icon) {
RoundedPanel card = new RoundedPanel(15, color, true);
card.setLayout(new BorderLayout());
card.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
card.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// Icon and title panel
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.setOpaque(false);
JLabel iconLabel = new JLabel(icon);
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 24));
JLabel titleLabel = new JLabel(title);
titleLabel.setForeground(Color.WHITE);
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 14));
topPanel.add(iconLabel, BorderLayout.WEST);
topPanel.add(titleLabel, BorderLayout.CENTER);
// Value label
JLabel valueLabel = new JLabel("--");
valueLabel.setForeground(Color.WHITE);
valueLabel.setFont(new Font("Segoe UI", Font.BOLD, 32));
valueLabel.setHorizontalAlignment(SwingConstants.CENTER);
card.add(topPanel, BorderLayout.NORTH);
card.add(valueLabel, BorderLayout.CENTER);
// Add hover effect
card.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
card.setBackground(color.darker());
}
@Override
public void mouseExited(MouseEvent e) {
card.setBackground(color);
}
});
return card;
}
private JPanel createCenterSection() {
JPanel centerSection = new JPanel(new GridLayout(1, 2, 20, 0));
centerSection.setOpaque(false);
centerSection.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
// Recent activities panel
JPanel activitiesPanel = createRecentActivitiesPanel();
// Quick actions panel
JPanel actionsPanel = createQuickActionsPanel();
centerSection.add(activitiesPanel);
centerSection.add(actionsPanel);
return centerSection;
}
private JPanel createRecentActivitiesPanel() {
RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel titleLabel = new JLabel("Hoạt động gần đây");
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18));
titleLabel.setForeground(new Color(31, 41, 55));
// Table for recent activities
String[] columns = {"Thời gian", "Bệnh nhân", "Hoạt động", "Trạng thái"};
DefaultTableModel tableModel = new DefaultTableModel(columns, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
recentActivitiesTable = new JTable(tableModel);
recentActivitiesTable.setRowHeight(35);
recentActivitiesTable.getTableHeader().setFont(new Font("Segoe UI", Font.BOLD, 12));
recentActivitiesTable.setFont(new Font("Segoe UI", Font.PLAIN, 12));
recentActivitiesTable.setSelectionBackground(new Color(219, 234, 254));
recentActivitiesTable.setGridColor(new Color(229, 231, 235));
JScrollPane tableScroll = new JScrollPane(recentActivitiesTable);
tableScroll.setBorder(BorderFactory.createEmptyBorder());
tableScroll.getViewport().setBackground(Color.WHITE);
panel.add(titleLabel, BorderLayout.NORTH);
panel.add(Box.createVerticalStrut(15), BorderLayout.NORTH);
panel.add(tableScroll, BorderLayout.CENTER);
return panel;
}
private JPanel createQuickActionsPanel() {
RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel titleLabel = new JLabel("Thao tác nhanh");
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18));
titleLabel.setForeground(new Color(31, 41, 55));
JPanel actionsGrid = new JPanel(new GridLayout(3, 2, 15, 15));
actionsGrid.setOpaque(false);
actionsGrid.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
// Quick action buttons
String[] actionTitles = {"Thêm bệnh nhân", "Xem hàng đợi", "Báo cáo ngày", "Tìm bệnh nhân", "Cài đặt", "Khẩn cấp"};
String[] actionIcons = {"➕", "📋", "📊", "🔍", "⚙️", "🚨"};
Color[] actionColors = {
new Color(34, 197, 94), // Green - Add patient
new Color(59, 130, 246), // Blue - View queue
new Color(168, 85, 247), // Purple - Reports
new Color(245, 158, 11), // Orange - Search
new Color(107, 114, 128), // Gray - Settings
new Color(239, 68, 68) // Red - Emergency
};
for (int i = 0; i < actionTitles.length; i++) {
JButton actionButton = createQuickActionButton(actionTitles[i], actionIcons[i], actionColors[i]);
final int index = i;
actionButton.addActionListener(e -> handleQuickAction(index));
actionsGrid.add(actionButton);
}
panel.add(titleLabel, BorderLayout.NORTH);
panel.add(actionsGrid, BorderLayout.CENTER);
return panel;
}
private JButton createQuickActionButton(String title, String icon, Color color) {
JButton button = new JButton();
button.setLayout(new BorderLayout());
button.setBackground(color);
button.setForeground(Color.WHITE);
button.setFont(new Font("Segoe UI", Font.BOLD, 12));
button.setFocusPainted(false);
button.setBorder(BorderFactory.createEmptyBorder(15, 10, 15, 10));
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
JLabel iconLabel = new JLabel(icon);
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
iconLabel.setForeground(Color.WHITE);
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
JLabel titleLabel = new JLabel(title);
titleLabel.setForeground(Color.WHITE);
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
button.add(iconLabel, BorderLayout.CENTER);
button.add(titleLabel, BorderLayout.SOUTH);
// Hover effect
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
button.setBackground(color.darker());
}
@Override
public void mouseExited(MouseEvent e) {
button.setBackground(color);
}
});
return button;
}
private JPanel createBottomSection() {
RoundedPanel panel = new RoundedPanel(15, Color.WHITE, false);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel titleLabel = new JLabel("Lịch hẹn hôm nay");
titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 18));
titleLabel.setForeground(new Color(31, 41, 55));
// TODO: Create appointment calendar widget
JPanel appointmentPanel = new JPanel(new BorderLayout());
appointmentPanel.setOpaque(false);
appointmentPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
JLabel placeholderLabel = new JLabel("Tính năng lịch hẹn đang được phát triển...");
placeholderLabel.setFont(new Font("Segoe UI", Font.ITALIC, 14));
placeholderLabel.setForeground(new Color(107, 114, 128));
placeholderLabel.setHorizontalAlignment(SwingConstants.CENTER);
appointmentPanel.add(placeholderLabel, BorderLayout.CENTER);
panel.add(titleLabel, BorderLayout.NORTH);
panel.add(appointmentPanel, BorderLayout.CENTER);
return panel;
}
// TODO: Implement backend data loading
private void loadDashboardData() {
// Placeholder data - replace with real backend calls
updateMetrics(12, 3, 156, 5);
loadRecentActivities();
}
private void updateMetrics(int todayPatients, int recheckPatients, int totalPatients, int pendingResults) {
if (todayPatientsLabel != null) todayPatientsLabel.setText(String.valueOf(todayPatients));
if (recheckPatientsLabel != null) recheckPatientsLabel.setText(String.valueOf(recheckPatients));
if (totalPatientsLabel != null) totalPatientsLabel.setText(String.valueOf(totalPatients));
if (pendingResultsLabel != null) pendingResultsLabel.setText(String.valueOf(pendingResults));
}
private void loadRecentActivities() {
DefaultTableModel model = (DefaultTableModel) recentActivitiesTable.getModel();
model.setRowCount(0); // Clear existing data
// TODO: Replace with real data from backend
String[][] sampleData = {
{"10:30", "Nguyễn Văn A", "Khám tổng quát", "Hoàn thành"},
{"10:15", "Trần Thị B", "Siêu âm", "Đang thực hiện"},
{"09:45", "Lê Văn C", "Tái khám", "Chờ kết quả"},
{"09:30", "Phạm Thị D", "Khám thai", "Hoàn thành"},
{"09:00", "Hoàng Văn E", "Khám tổng quát", "Hoàn thành"}
};
for (String[] row : sampleData) {
model.addRow(row);
}
}
private void refreshDashboardData() {
lastUpdateLabel.setText("Cập nhật lần cuối: " + getCurrentTime());
// TODO: Implement real data refresh from backend
loadDashboardData();
JOptionPane.showMessageDialog(this,
"Đã làm mới dữ liệu thành công!",
"Thông báo",
JOptionPane.INFORMATION_MESSAGE);
}
private void handleQuickAction(int actionIndex) {
String[] actions = {"AddPatient", "CheckUpPage", "Reports", "SearchPatient", "SettingsPage", "Emergency"};
String[] messages = {
"Chuyển đến trang thêm bệnh nhân...",
"Chuyển đến trang xem hàng đợi...",
"Mở báo cáo ngày...",
"Mở tìm kiếm bệnh nhân...",
"Chuyển đến trang cài đặt...",
"Kích hoạt chế độ khẩn cấp!"
};
// TODO: Implement real navigation and actions
switch (actionIndex) {
case 0: // Add Patient - Navigate to patient data page
mainFrame.showPage("PatientDataPage");
break;
case 1: // View Queue - Navigate to checkup page
mainFrame.showPage("CheckUpPage");
break;
case 4: // Settings - Navigate to settings page
mainFrame.showPage("SettingsPage");
break;
default:
JOptionPane.showMessageDialog(this,
messages[actionIndex],
"Thông báo",
JOptionPane.INFORMATION_MESSAGE);
}
}
private String getCurrentTime() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"));
}
// 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(1200, 800);
// DashboardPage d = new DashboardPage(null); // Pass null or a mock MainFrame for testing
// frame.add(d);
// frame.setVisible(true);
// Test frame for development
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Dashboard Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1400, 900);
frame.setLocationRelativeTo(null);
LocalStorage.username = "Test User"; // Set test username
DashboardPage dashboard = new DashboardPage(null);
frame.add(dashboard);
frame.setVisible(true);
});
}
}
@@ -0,0 +1,158 @@
package BsK.client.ui.component.InfoPage;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
public class InfoDialog extends JDialog {
public InfoDialog() {
setTitle("Thông tin dự án BSK Clinic Management System");
setModal(true);
setSize(900, 900);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
setResizable(true);
// Create main content panel with better styling
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBackground(Color.WHITE);
mainPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createRaisedBevelBorder(),
new EmptyBorder(20, 20, 20, 20)
));
String htmlContent = "<html>"
+ "<body style='font-family: Segoe UI, Arial, sans-serif; line-height: 1.6; color: #333; background-color: #ffffff;'>"
+ "<div style='text-align: center; margin-bottom: 25px;'>"
+ "<h1 style='color: #2c5282; font-size: 24px; margin-bottom: 10px; font-weight: bold;'>BSK CLINIC MANAGEMENT SYSTEM</h1>"
+ "<hr style='border: 2px solid #4299e1; width: 60%; margin: 20px auto;'>"
+ "</div>"
+ "<div style='margin-bottom: 10px;'>"
+ "<h2 style='color: #2d3748; font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;'>Giới thiệu</h2>"
+ "<p style='font-size: 14px; text-align: justify; margin-bottom: 15px;'>BSK là phần mềm quản lý phòng khám toàn diện, chuyên biệt hỗ trợ theo dõi bệnh nhân và in kết quả siêu âm. "
+ "Dự án được phát triển bởi <strong style='color: #2c5282;'>Lê Thành Đạt </strong> "
+ "(<a href='https://github.com/lds217' style='color: #3182ce; text-decoration: underline;'>lds</a>) "
+ "trong những năm đầu đại học với mục tiêu rèn luyện kỹ năng lập trình Java và xử lý backend, "
+ "cùng với sự hỗ trợ quý báu từ <strong style='color: #2c5282;'>Nguyễn Minh Tiến </strong> "
+ "(<a href='https://github.com/tiennm99' style='color: #3182ce; text-decoration: underline;'>tiennm99</a>).</p>"
+ "<p style='font-size: 14px; text-align: justify;'>Phần mềm hướng tới mục tiêu hiện đại hóa quy trình khám chữa bệnh, đặc biệt trong lĩnh vực siêu âm và sản khoa, "
+ "giúp bệnh nhân dễ dàng tiếp cận thông tin và dịch vụ y tế chất lượng cao.</p>"
+ "</div>"
+ "<div style='margin-bottom: 25px;'>"
+ "<h2 style='color: #2d3748; font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;'>Tính năng nổi bật</h2>"
+ "<div style='background-color: #f7fafc; padding: 15px; border-radius: 8px; border-left: 4px solid #4299e1;'>"
+ "<ul style='font-size: 14px; margin: 0; padding-left: 20px;'>"
+ "<li style='margin-bottom: 12px;'><strong style='color: #2c5282;'>Kết nối mạng nội bộ:</strong> Cho phép các máy tính trong phòng khám kết nối liền mạch và đồng bộ dữ liệu.</li>"
+ "<li style='margin-bottom: 12px;'><strong style='color: #2c5282;'>Tự động phân phối ảnh siêu âm:</strong> Tự động theo dõi và gửi hình ảnh siêu âm đến đúng bệnh nhân một cách chính xác.</li>"
+ "<li style='margin-bottom: 12px;'><strong style='color: #2c5282;'>Giao diện hiện đại:</strong> Thiết kế giao diện thân thiện, trực quan và dễ sử dụng cho mọi đối tượng người dùng.</li>"
+ "<li style='margin-bottom: 12px;'><strong style='color: #2c5282;'>Tích hợp Google Drive:</strong> Dễ dàng tải kết quả khám lên Google Drive và chia sẻ trực tiếp với bệnh nhân.</li>"
+ "<li style='margin-bottom: 0;'><strong style='color: #2c5282;'>Thông báo tái khám thông minh:</strong> Hệ thống nhắc nhở bán tự động giúp bệnh nhân không bỏ lỡ lịch tái khám.</li>"
+ "</ul>"
+ "</div>"
+ "</div>"
+ "<div style='margin-bottom: 25px;'>"
+ "<h2 style='color: #2d3748; font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;'>Sứ mệnh</h2>"
+ "<div style='background-color: #f0fff4; padding: 15px; border-radius: 8px; border-left: 4px solid #48bb78;'>"
+ "<p style='font-size: 14px; text-align: justify; margin: 0;'>Đây là một dự án mã nguồn mở được xây dựng cho mục đích sử dụng nội bộ và nghiên cứu. "
+ "Chúng tôi khuyến khích các phòng khám, bệnh viện ứng dụng công nghệ hiện đại để nâng cao chất lượng dịch vụ, "
+ "cải thiện trải nghiệm của bệnh nhân, từ đó góp phần vào sự phát triển bền vững của nền y tế Việt Nam.</p>"
+ "</div>"
+ "</div>"
+ "<div style='margin-bottom: 25px;'>"
+ "<h2 style='color: #2d3748; font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;'>Bản quyền và Liên hệ</h2>"
+ "<div style='background-color: #edf2f7; padding: 15px; border-radius: 8px;'>"
+ "<p style='font-size: 14px; margin-bottom: 15px;'>Dự án được phát triển và sở hữu bởi <strong style='color: #2c5282;'>Lê Thành Đạt (lds)</strong>. "
+ "Mọi ý kiến đóng góp, phản hồi hoặc câu hỏi xin vui lòng liên hệ qua:</p>"
+ "<ul style='font-size: 14px; margin: 0; padding-left: 20px;'>"
+ "<li style='margin-bottom: 8px;'><strong>GitHub:</strong> <a href='https://github.com/lds217' style='color: #3182ce; text-decoration: underline;'>lds217</a></li>"
+ "<li style='margin-bottom: 0;'><strong>LinkedIn:</strong> <a href='https://www.linkedin.com/in/ldss21/' style='color: #3182ce; text-decoration: underline;'>Lê Thành Đạt</a></li>"
+ "</ul>"
+ "</div>"
+ "</div>"
+ "<div style='margin-bottom: 10px;'>"
+ "<h2 style='color: #2d3748; font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;'>Cách đóng góp</h2>"
+ "<div style='background-color: #fffaf0; padding: 15px; border-radius: 8px; border-left: 4px solid #ed8936;'>"
+ "<p style='font-size: 14px; margin-bottom: 15px;'>Chúng tôi luôn chào đón và trân trọng mọi sự đóng góp để cải thiện phần mềm. Bạn có thể hỗ trợ dự án bằng cách:</p>"
+ "<ul style='font-size: 14px; margin: 0; padding-left: 20px;'>"
+ "<li style='margin-bottom: 10px;'>Đề xuất tính năng mới hoặc báo cáo lỗi thông qua GitHub Issues.</li>"
+ "<li style='margin-bottom: 10px;'>Đóng góp code thông qua Pull Requests.</li>"
+ "<li style='margin-bottom: 0;'>Ủng hộ dự án một ly cà phê qua <strong style='color: #d69e2e;'>MB Bank: 0908308878</strong></li>"
+ "</ul>"
+ "</div>"
+ "</div>"
+ "</body></html>";
JEditorPane editorPane = new JEditorPane("text/html", htmlContent);
editorPane.setEditable(false);
editorPane.setOpaque(true);
editorPane.setBackground(Color.WHITE);
editorPane.setFont(new Font("Segoe UI", Font.PLAIN, 14));
editorPane.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(e.getURL().toURI());
} catch (IOException | URISyntaxException ex) {
ex.printStackTrace();
}
}
}
}
});
JScrollPane scrollPane = new JScrollPane(editorPane);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
scrollPane.setBackground(Color.WHITE);
scrollPane.getViewport().setBackground(Color.WHITE);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
mainPanel.add(scrollPane, BorderLayout.CENTER);
// Create button panel with better styling
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttonPanel.setBackground(Color.WHITE);
buttonPanel.setBorder(new EmptyBorder(15, 0, 0, 0));
JButton closeButton = new JButton("Đóng");
closeButton.setFont(new Font("Segoe UI", Font.BOLD, 14));
closeButton.setPreferredSize(new Dimension(100, 35));
closeButton.setBackground(new Color(44, 82, 130));
closeButton.setForeground(Color.WHITE);
closeButton.setFocusPainted(false);
closeButton.setBorder(BorderFactory.createRaisedBevelBorder());
closeButton.addActionListener(e -> dispose());
buttonPanel.add(closeButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
add(mainPanel, BorderLayout.CENTER);
}
}
@@ -1,570 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import BsK.client.ui.component.InventoryPage.common.InventoryButtonFactory;
import BsK.client.ui.component.InventoryPage.common.InventoryColorScheme;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@Slf4j
public class GoodsReceiptPanel extends JPanel {
private InventoryPage parentPage;
// Header information
private JComboBox<String> supplierCombo;
private JTextField invoiceNumberField;
private JTextField receiptDateField;
// Medicine search and add
private JTextField medicineSearchField;
private JButton addMedicineButton;
// Receipt items table
private JTable receiptTable;
private DefaultTableModel receiptTableModel;
// Total and actions
private JLabel totalAmountLabel;
private JButton saveReceiptButton;
private JButton cancelButton;
private JButton printButton;
// Formatters
private SimpleDateFormat dateFormatter;
private NumberFormat currencyFormatter;
// Current receipt data
private double totalAmount = 0.0;
public GoodsReceiptPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy");
this.currencyFormatter = NumberFormat.getNumberInstance(new Locale("vi", "VN"));
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Supplier combo
supplierCombo = new JComboBox<>(new String[]{
"Chọn nhà cung cấp...",
"Công ty TNHH Dược phẩm ABC",
"Công ty Cổ phần Dược Hậu Giang",
"Công ty TNHH Dược phẩm Traphaco",
"Công ty Cổ phần Dược phẩm OPC",
"Công ty TNHH Dược phẩm Imexpharm"
});
supplierCombo.setFont(new Font("Arial", Font.PLAIN, 14));
supplierCombo.setPreferredSize(new Dimension(300, 35));
// Invoice number field
invoiceNumberField = new JTextField();
invoiceNumberField.setFont(new Font("Arial", Font.PLAIN, 14));
invoiceNumberField.setPreferredSize(new Dimension(200, 35));
invoiceNumberField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
// Receipt date field (auto-filled with today)
receiptDateField = new JTextField(dateFormatter.format(new Date()));
receiptDateField.setFont(new Font("Arial", Font.PLAIN, 14));
receiptDateField.setPreferredSize(new Dimension(150, 35));
receiptDateField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
// Medicine search field with placeholder
medicineSearchField = new JTextField("Tìm thuốc theo tên hoặc quét mã vạch...");
medicineSearchField.setFont(new Font("Arial", Font.ITALIC, 14));
medicineSearchField.setForeground(InventoryColorScheme.TEXT_DISABLED);
medicineSearchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(InventoryColorScheme.BORDER_LIGHT, 2),
BorderFactory.createEmptyBorder(12, 15, 12, 15)
));
// Add focus listener for placeholder behavior
medicineSearchField.addFocusListener(new java.awt.event.FocusAdapter() {
@Override
public void focusGained(java.awt.event.FocusEvent e) {
if (medicineSearchField.getText().equals("Tìm thuốc theo tên hoặc quét mã vạch...")) {
medicineSearchField.setText("");
medicineSearchField.setFont(new Font("Arial", Font.PLAIN, 14));
medicineSearchField.setForeground(InventoryColorScheme.TEXT_PRIMARY);
medicineSearchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(InventoryColorScheme.PRIMARY_BLUE, 2),
BorderFactory.createEmptyBorder(12, 15, 12, 15)
));
}
}
@Override
public void focusLost(java.awt.event.FocusEvent e) {
if (medicineSearchField.getText().trim().isEmpty()) {
medicineSearchField.setText("Tìm thuốc theo tên hoặc quét mã vạch...");
medicineSearchField.setFont(new Font("Arial", Font.ITALIC, 14));
medicineSearchField.setForeground(InventoryColorScheme.TEXT_DISABLED);
medicineSearchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(InventoryColorScheme.BORDER_LIGHT, 2),
BorderFactory.createEmptyBorder(12, 15, 12, 15)
));
}
}
});
// Receipt table
String[] columns = {"Tên Thuốc", "Lô", "Hạn Sử Dụng", "Số Lượng", "Đơn Giá Nhập", "Thành Tiền", "Xóa"};
receiptTableModel = new DefaultTableModel(columns, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return column == 6; // Only delete column is editable
}
};
receiptTable = new JTable(receiptTableModel);
setupReceiptTable();
// Total amount label
totalAmountLabel = new JLabel("0 VNĐ");
totalAmountLabel.setFont(new Font("Arial", Font.BOLD, 24));
totalAmountLabel.setForeground(new Color(76, 175, 80));
totalAmountLabel.setHorizontalAlignment(SwingConstants.RIGHT);
// Action buttons using standardized factory
saveReceiptButton = InventoryButtonFactory.createPrimaryButton("💾 Lưu Phiếu Nhập", InventoryButtonFactory.LARGE_BUTTON_SIZE);
cancelButton = InventoryButtonFactory.createSecondaryButton("❌ Hủy Bỏ", InventoryButtonFactory.LARGE_BUTTON_SIZE);
printButton = InventoryButtonFactory.createSpecialButton("🖨️ In Phiếu", InventoryButtonFactory.LARGE_BUTTON_SIZE);
printButton.setEnabled(false);
}
private void setupReceiptTable() {
receiptTable.setFont(new Font("Arial", Font.PLAIN, 14));
receiptTable.setRowHeight(40);
receiptTable.setBackground(Color.WHITE);
receiptTable.setGridColor(new Color(230, 230, 230));
receiptTable.setShowVerticalLines(true);
receiptTable.setShowHorizontalLines(true);
receiptTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
receiptTable.getTableHeader().setBackground(InventoryColorScheme.TABLE_HEADER_BG);
receiptTable.getTableHeader().setForeground(InventoryColorScheme.TABLE_HEADER_TEXT);
receiptTable.getTableHeader().setReorderingAllowed(false);
// Set column widths
TableColumn col0 = receiptTable.getColumnModel().getColumn(0); // Tên Thuốc
col0.setPreferredWidth(200);
TableColumn col1 = receiptTable.getColumnModel().getColumn(1); // Lô
col1.setPreferredWidth(100);
TableColumn col2 = receiptTable.getColumnModel().getColumn(2); // HSD
col2.setPreferredWidth(100);
TableColumn col3 = receiptTable.getColumnModel().getColumn(3); // Số Lượng
col3.setPreferredWidth(100);
TableColumn col4 = receiptTable.getColumnModel().getColumn(4); // Đơn giá
col4.setPreferredWidth(120);
TableColumn col5 = receiptTable.getColumnModel().getColumn(5); // Thành tiền
col5.setPreferredWidth(140);
TableColumn col6 = receiptTable.getColumnModel().getColumn(6); // Xóa
col6.setPreferredWidth(80);
// Center align specific columns
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
receiptTable.getColumnModel().getColumn(1).setCellRenderer(centerRenderer);
receiptTable.getColumnModel().getColumn(2).setCellRenderer(centerRenderer);
receiptTable.getColumnModel().getColumn(3).setCellRenderer(centerRenderer);
// Right align money columns
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
receiptTable.getColumnModel().getColumn(4).setCellRenderer(rightRenderer);
receiptTable.getColumnModel().getColumn(5).setCellRenderer(rightRenderer);
// Delete button column
receiptTable.getColumnModel().getColumn(6).setCellRenderer(new DeleteButtonRenderer());
receiptTable.getColumnModel().getColumn(6).setCellEditor(new DeleteButtonEditor());
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("PHIẾU NHẬP KHO", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Content area
JPanel contentPanel = new JPanel(new BorderLayout(0, 20));
contentPanel.setOpaque(false);
// Header info panel
RoundedPanel headerPanel = createHeaderPanel();
contentPanel.add(headerPanel, BorderLayout.NORTH);
// Medicine items panel
RoundedPanel itemsPanel = createItemsPanel();
contentPanel.add(itemsPanel, BorderLayout.CENTER);
// Footer with total and actions
RoundedPanel footerPanel = createFooterPanel();
contentPanel.add(footerPanel, BorderLayout.SOUTH);
mainPanel.add(contentPanel, BorderLayout.CENTER);
add(mainPanel, BorderLayout.CENTER);
}
private RoundedPanel createHeaderPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 15);
gbc.anchor = GridBagConstraints.WEST;
// Row 1: Supplier
gbc.gridx = 0; gbc.gridy = 0;
panel.add(new JLabel("Nhà cung cấp:"), gbc);
gbc.gridx = 1; gbc.gridwidth = 2;
panel.add(supplierCombo, gbc);
// Row 2: Invoice number and date
gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1;
panel.add(new JLabel("Số Hóa Đơn:"), gbc);
gbc.gridx = 1;
panel.add(invoiceNumberField, gbc);
gbc.gridx = 2;
panel.add(new JLabel("Ngày nhập:"), gbc);
gbc.gridx = 3;
panel.add(receiptDateField, gbc);
return panel;
}
private RoundedPanel createItemsPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Simplified medicine search section
JPanel searchPanel = new JPanel(new BorderLayout(10, 0));
searchPanel.setOpaque(false);
searchPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0));
JLabel searchIcon = new JLabel("🔍");
searchIcon.setFont(new Font("Arial", Font.PLAIN, 16));
searchPanel.add(searchIcon, BorderLayout.WEST);
medicineSearchField.setPreferredSize(new Dimension(0, 45));
searchPanel.add(medicineSearchField, BorderLayout.CENTER);
panel.add(searchPanel, BorderLayout.NORTH);
// Items table
JScrollPane scrollPane = new JScrollPane(receiptTable);
scrollPane.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
" Danh sách thuốc nhập kho ",
0, 0, new Font("Arial", Font.BOLD, 14), new Color(37, 47, 63)
));
scrollPane.getViewport().setBackground(Color.WHITE);
scrollPane.setPreferredSize(new Dimension(0, 300));
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private RoundedPanel createFooterPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(20, 0));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Total section
JPanel totalPanel = new JPanel(new BorderLayout());
totalPanel.setOpaque(false);
JLabel totalLabel = new JLabel("TỔNG TIỀN NHẬP KHO:");
totalLabel.setFont(new Font("Arial", Font.BOLD, 18));
totalLabel.setForeground(new Color(37, 47, 63));
totalPanel.add(totalLabel, BorderLayout.WEST);
totalPanel.add(totalAmountLabel, BorderLayout.EAST);
// Actions panel with split layout (Cancel left, Print/Save right)
JButton[] leftButtons = {cancelButton};
JButton[] rightButtons = {printButton, saveReceiptButton};
JPanel actionsPanel = InventoryButtonFactory.createSplitButtonGroup(leftButtons, rightButtons);
panel.add(totalPanel, BorderLayout.WEST);
panel.add(actionsPanel, BorderLayout.EAST);
return panel;
}
private void setupEventListeners() {
// Medicine search field - Enter key adds medicine
medicineSearchField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
addMedicineToReceipt();
}
}
});
// Action buttons
saveReceiptButton.addActionListener(e -> saveReceipt());
cancelButton.addActionListener(e -> cancelReceipt());
printButton.addActionListener(e -> printReceipt());
}
private void addMedicineToReceipt() {
String searchText = medicineSearchField.getText().trim();
if (searchText.isEmpty() || searchText.equals("Tìm thuốc theo tên hoặc quét mã vạch...")) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập tên thuốc hoặc quét mã vạch!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
return;
}
// TODO: Search medicine in database and show selection dialog
showAddMedicineDialog(searchText);
}
private void showAddMedicineDialog(String searchText) {
// Mock implementation - show a simple dialog for adding medicine details
JDialog dialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(this), "Thêm Thuốc Vào Phiếu Nhập", true);
dialog.setLayout(new BorderLayout());
dialog.setSize(500, 400);
dialog.setLocationRelativeTo(this);
JPanel formPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.anchor = GridBagConstraints.WEST;
// Medicine name (read-only, from search)
gbc.gridx = 0; gbc.gridy = 0;
formPanel.add(new JLabel("Tên thuốc:"), gbc);
gbc.gridx = 1;
JTextField nameField = new JTextField(searchText, 20);
nameField.setEditable(false);
formPanel.add(nameField, gbc);
// Batch number
gbc.gridx = 0; gbc.gridy = 1;
formPanel.add(new JLabel("Số lô:"), gbc);
gbc.gridx = 1;
JTextField batchField = new JTextField(20);
formPanel.add(batchField, gbc);
// Expiry date
gbc.gridx = 0; gbc.gridy = 2;
formPanel.add(new JLabel("Hạn sử dụng:"), gbc);
gbc.gridx = 1;
JTextField expiryField = new JTextField("dd/MM/yyyy", 20);
formPanel.add(expiryField, gbc);
// Quantity
gbc.gridx = 0; gbc.gridy = 3;
formPanel.add(new JLabel("Số lượng:"), gbc);
gbc.gridx = 1;
JSpinner quantitySpinner = new JSpinner(new SpinnerNumberModel(1, 1, 9999, 1));
formPanel.add(quantitySpinner, gbc);
// Unit price
gbc.gridx = 0; gbc.gridy = 4;
formPanel.add(new JLabel("Đơn giá nhập:"), gbc);
gbc.gridx = 1;
JTextField priceField = new JTextField("0", 20);
formPanel.add(priceField, gbc);
// Buttons
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton addBtn = new JButton("Thêm");
JButton cancelBtn = new JButton("Hủy");
addBtn.addActionListener(e -> {
try {
String medicineName = nameField.getText();
String batch = batchField.getText();
String expiry = expiryField.getText();
int quantity = (Integer) quantitySpinner.getValue();
double price = Double.parseDouble(priceField.getText().replace(",", ""));
double lineTotal = quantity * price;
// Add to table
receiptTableModel.addRow(new Object[]{
medicineName,
batch,
expiry,
quantity,
currencyFormatter.format(price),
currencyFormatter.format(lineTotal),
"🗑️"
});
updateTotalAmount();
medicineSearchField.setText("");
dialog.dispose();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(dialog, "Vui lòng nhập đúng định dạng số!",
"Lỗi", JOptionPane.ERROR_MESSAGE);
}
});
cancelBtn.addActionListener(e -> dialog.dispose());
buttonPanel.add(addBtn);
buttonPanel.add(cancelBtn);
dialog.add(formPanel, BorderLayout.CENTER);
dialog.add(buttonPanel, BorderLayout.SOUTH);
dialog.setVisible(true);
}
private void updateTotalAmount() {
totalAmount = 0.0;
for (int i = 0; i < receiptTableModel.getRowCount(); i++) {
String lineTotalStr = (String) receiptTableModel.getValueAt(i, 5);
try {
double lineTotal = Double.parseDouble(lineTotalStr.replace(",", ""));
totalAmount += lineTotal;
} catch (NumberFormatException e) {
log.warn("Could not parse line total: " + lineTotalStr);
}
}
totalAmountLabel.setText(currencyFormatter.format(totalAmount) + " VNĐ");
}
private void saveReceipt() {
if (receiptTableModel.getRowCount() == 0) {
JOptionPane.showMessageDialog(this, "Vui lòng thêm ít nhất một loại thuốc!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
return;
}
if (supplierCombo.getSelectedIndex() == 0) {
JOptionPane.showMessageDialog(this, "Vui lòng chọn nhà cung cấp!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
return;
}
// TODO: Save receipt to database
JOptionPane.showMessageDialog(this, "Lưu phiếu nhập kho thành công!\nTổng tiền: " + totalAmountLabel.getText(),
"Thành công", JOptionPane.INFORMATION_MESSAGE);
printButton.setEnabled(true);
clearForm();
}
private void cancelReceipt() {
int confirm = JOptionPane.showConfirmDialog(this,
"Bạn có chắc chắn muốn hủy phiếu nhập này?",
"Xác nhận", JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
clearForm();
}
}
private void printReceipt() {
// TODO: Implement print functionality
JOptionPane.showMessageDialog(this, "Chức năng in phiếu nhập kho đang được phát triển!",
"Thông báo", JOptionPane.INFORMATION_MESSAGE);
}
private void clearForm() {
supplierCombo.setSelectedIndex(0);
invoiceNumberField.setText("");
receiptDateField.setText(dateFormatter.format(new Date()));
// Reset search field to placeholder
medicineSearchField.setText("Tìm thuốc theo tên hoặc quét mã vạch...");
medicineSearchField.setFont(new Font("Arial", Font.ITALIC, 14));
medicineSearchField.setForeground(InventoryColorScheme.TEXT_DISABLED);
medicineSearchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(InventoryColorScheme.BORDER_LIGHT, 2),
BorderFactory.createEmptyBorder(12, 15, 12, 15)
));
receiptTableModel.setRowCount(0);
totalAmount = 0.0;
totalAmountLabel.setText("0 VNĐ");
printButton.setEnabled(false);
}
public void refreshData() {
// TODO: Refresh supplier list and other master data
log.info("Goods receipt panel data refreshed");
}
// Custom cell renderer for delete button
private class DeleteButtonRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
return InventoryButtonFactory.createIconButton(
InventoryButtonFactory.ICON_DELETE,
InventoryColorScheme.DANGER_RED,
InventoryColorScheme.darken(InventoryColorScheme.DANGER_RED, 0.2f),
"Xóa dòng này"
);
}
}
// Custom cell editor for delete button
private class DeleteButtonEditor extends DefaultCellEditor {
private JButton button;
private int currentRow;
public DeleteButtonEditor() {
super(new JCheckBox());
button = InventoryButtonFactory.createIconButton(
InventoryButtonFactory.ICON_DELETE,
InventoryColorScheme.DANGER_RED,
InventoryColorScheme.darken(InventoryColorScheme.DANGER_RED, 0.2f),
"Xóa dòng này"
);
button.addActionListener(e -> {
receiptTableModel.removeRow(currentRow);
updateTotalAmount();
fireEditingStopped();
});
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
currentRow = row;
return button;
}
@Override
public Object getCellEditorValue() {
return "🗑️";
}
}
}
@@ -1,228 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import BsK.client.ui.component.InventoryPage.common.InventoryButtonFactory;
import BsK.client.ui.component.InventoryPage.common.InventoryColorScheme;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@Slf4j
public class InventoryDashboard extends JPanel {
private InventoryPage parentPage;
// Dashboard metrics
private JLabel lowStockCountLabel;
private JLabel expiringCountLabel;
private JLabel totalValueLabel;
private JLabel totalItemsLabel;
// Quick action buttons
private JButton newReceiptButton;
private JButton viewInventoryButton;
private JButton lowStockReportButton;
private JButton expiryReportButton;
public InventoryDashboard(InventoryPage parentPage) {
this.parentPage = parentPage;
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Initialize metric labels with refined colors
lowStockCountLabel = new JLabel("0");
lowStockCountLabel.setFont(new Font("Arial", Font.BOLD, 42));
lowStockCountLabel.setForeground(InventoryColorScheme.DANGER_RED);
lowStockCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
expiringCountLabel = new JLabel("0");
expiringCountLabel.setFont(new Font("Arial", Font.BOLD, 42));
expiringCountLabel.setForeground(InventoryColorScheme.WARNING_ORANGE);
expiringCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
totalValueLabel = new JLabel("0 VNĐ");
totalValueLabel.setFont(new Font("Arial", Font.BOLD, 28));
totalValueLabel.setForeground(InventoryColorScheme.SUCCESS_GREEN);
totalValueLabel.setHorizontalAlignment(SwingConstants.CENTER);
totalItemsLabel = new JLabel("0");
totalItemsLabel.setFont(new Font("Arial", Font.BOLD, 42));
totalItemsLabel.setForeground(InventoryColorScheme.PRIMARY_BLUE);
totalItemsLabel.setHorizontalAlignment(SwingConstants.CENTER);
// Initialize quick action buttons using standardized factory
newReceiptButton = InventoryButtonFactory.createSuccessButton("📥 Nhập Kho Mới", InventoryButtonFactory.LARGE_BUTTON_SIZE);
viewInventoryButton = InventoryButtonFactory.createPrimaryButton("📦 Xem Tồn Kho", InventoryButtonFactory.LARGE_BUTTON_SIZE);
lowStockReportButton = InventoryButtonFactory.createDestructiveButton("⚠️ Thuốc Sắp Hết", InventoryButtonFactory.LARGE_BUTTON_SIZE);
expiryReportButton = InventoryButtonFactory.createSpecialButton("📅 Sắp Hết Hạn", InventoryButtonFactory.LARGE_BUTTON_SIZE);
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(20, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("Tổng Quan Kho Thuốc", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Metrics cards
JPanel metricsPanel = createMetricsPanel();
mainPanel.add(metricsPanel, BorderLayout.CENTER);
// Quick actions
JPanel actionsPanel = createActionsPanel();
mainPanel.add(actionsPanel, BorderLayout.SOUTH);
add(mainPanel, BorderLayout.CENTER);
}
private JPanel createMetricsPanel() {
JPanel panel = new JPanel(new GridLayout(2, 2, 20, 20));
panel.setOpaque(false);
// Low Stock Card
RoundedPanel lowStockCard = createMetricCard(
"Thuốc Sắp Hết",
lowStockCountLabel,
"sản phẩm dưới mức tối thiểu",
InventoryColorScheme.CARD_LOW_STOCK_BORDER,
InventoryColorScheme.CARD_LOW_STOCK_BG
);
// Expiring Soon Card
RoundedPanel expiringCard = createMetricCard(
"Sắp Hết Hạn",
expiringCountLabel,
"lô thuốc trong 3 tháng tới",
InventoryColorScheme.CARD_EXPIRING_BORDER,
InventoryColorScheme.CARD_EXPIRING_BG
);
// Total Value Card
RoundedPanel valueCard = createMetricCard(
"Tổng Giá Trị Kho",
totalValueLabel,
"tại thời điểm hiện tại",
InventoryColorScheme.CARD_VALUE_BORDER,
InventoryColorScheme.CARD_VALUE_BG
);
// Total Items Card
RoundedPanel itemsCard = createMetricCard(
"Tổng Số Mặt Hàng",
totalItemsLabel,
"loại thuốc đang quản lý",
InventoryColorScheme.CARD_ITEMS_BORDER,
InventoryColorScheme.CARD_ITEMS_BG
);
panel.add(lowStockCard);
panel.add(expiringCard);
panel.add(valueCard);
panel.add(itemsCard);
return panel;
}
private RoundedPanel createMetricCard(String title, JLabel valueLabel, String subtitle, Color accentColor, Color bgColor) {
RoundedPanel card = new RoundedPanel(15, bgColor, true);
card.setLayout(new BorderLayout(5, 5));
card.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(accentColor, 2),
BorderFactory.createEmptyBorder(25, 20, 25, 20)
));
card.setPreferredSize(new Dimension(280, 160));
// Title
JLabel titleLabel = new JLabel(title, SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
titleLabel.setForeground(accentColor);
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
card.add(titleLabel, BorderLayout.NORTH);
// Value (centered with more space)
JPanel valuePanel = new JPanel(new GridBagLayout());
valuePanel.setOpaque(false);
valueLabel.setHorizontalAlignment(SwingConstants.CENTER);
valueLabel.setVerticalAlignment(SwingConstants.CENTER);
valuePanel.add(valueLabel);
card.add(valuePanel, BorderLayout.CENTER);
// Subtitle
JLabel subtitleLabel = new JLabel(subtitle, SwingConstants.CENTER);
subtitleLabel.setFont(new Font("Arial", Font.PLAIN, 12));
subtitleLabel.setForeground(InventoryColorScheme.TEXT_SECONDARY);
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
card.add(subtitleLabel, BorderLayout.SOUTH);
return card;
}
private JPanel createActionsPanel() {
JPanel panel = new JPanel(new BorderLayout(20, 20));
panel.setOpaque(false);
panel.setBorder(BorderFactory.createEmptyBorder(30, 0, 0, 0));
// Actions title
JLabel actionsTitle = new JLabel("Thao Tác Nhanh", SwingConstants.CENTER);
actionsTitle.setFont(new Font("Arial", Font.BOLD, 20));
actionsTitle.setForeground(new Color(37, 47, 63));
panel.add(actionsTitle, BorderLayout.NORTH);
// Action buttons with improved layout
JPanel buttonsPanel = new JPanel(new GridLayout(2, 2, 20, 20));
buttonsPanel.setOpaque(false);
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 10, 30));
buttonsPanel.add(newReceiptButton);
buttonsPanel.add(viewInventoryButton);
buttonsPanel.add(lowStockReportButton);
buttonsPanel.add(expiryReportButton);
panel.add(buttonsPanel, BorderLayout.CENTER);
return panel;
}
private void setupEventListeners() {
newReceiptButton.addActionListener(e -> parentPage.navigateToGoodsReceipt());
viewInventoryButton.addActionListener(e -> parentPage.navigateToInventoryView());
lowStockReportButton.addActionListener(e -> {
parentPage.navigateToReports();
// TODO: Auto-generate low stock report
});
expiryReportButton.addActionListener(e -> {
parentPage.navigateToReports();
// TODO: Auto-generate expiry report
});
}
public void refreshData() {
// TODO: Load actual data from database
loadMockDashboardData();
}
private void loadMockDashboardData() {
// Mock data for demonstration
lowStockCountLabel.setText("7");
expiringCountLabel.setText("12");
totalValueLabel.setText(parentPage.getCurrencyFormatter().format(45600000) + " VNĐ");
totalItemsLabel.setText("156");
log.info("Dashboard metrics refreshed");
}
}
@@ -1,281 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.LocalStorage;
import BsK.client.ui.component.MainFrame;
import BsK.client.ui.component.common.NavBar;
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 javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@Slf4j
public class InventoryPage extends JPanel {
private MainFrame mainFrame;
// Navigation components
private JPanel sideNavPanel;
private JPanel contentPanel;
private CardLayout cardLayout;
// Navigation buttons
private JButton dashboardButton;
private JButton inventoryViewButton;
private JButton goodsReceiptButton;
private JButton stockAdjustmentButton;
private JButton reportsButton;
private JButton productMasterButton;
private JButton supplierMasterButton;
// Sub-panels for each section
private InventoryDashboard dashboardPanel;
private InventoryViewPanel inventoryViewPanel;
private GoodsReceiptPanel goodsReceiptPanel;
private StockAdjustmentPanel stockAdjustmentPanel;
private ReportsPanel reportsPanel;
private ProductMasterPanel productMasterPanel;
private SupplierMasterPanel supplierMasterPanel;
// Common utilities
private NumberFormat currencyFormatter;
private SimpleDateFormat dateFormatter;
// Current selected section
private String currentSection = "dashboard";
public InventoryPage(MainFrame mainFrame) {
this.mainFrame = mainFrame;
this.currencyFormatter = NumberFormat.getNumberInstance(new Locale("vi", "VN"));
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy");
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
// Navigation Bar
NavBar navBar = new NavBar(mainFrame, "Quản Lý Kho Thuốc");
add(navBar, BorderLayout.NORTH);
initializeComponents();
layoutComponents();
setupEventListeners();
// Show dashboard by default
showSection("dashboard");
}
private void initializeComponents() {
// Initialize side navigation
createSideNavigation();
// Initialize content area with card layout
cardLayout = new CardLayout();
contentPanel = new JPanel(cardLayout);
contentPanel.setBackground(Color.WHITE);
// Initialize all panels
dashboardPanel = new InventoryDashboard(this);
inventoryViewPanel = new InventoryViewPanel(this);
goodsReceiptPanel = new GoodsReceiptPanel(this);
stockAdjustmentPanel = new StockAdjustmentPanel(this);
reportsPanel = new ReportsPanel(this);
productMasterPanel = new ProductMasterPanel(this);
supplierMasterPanel = new SupplierMasterPanel(this);
// Add panels to card layout
contentPanel.add(dashboardPanel, "dashboard");
contentPanel.add(inventoryViewPanel, "inventory");
contentPanel.add(goodsReceiptPanel, "receipt");
contentPanel.add(stockAdjustmentPanel, "adjustment");
contentPanel.add(reportsPanel, "reports");
contentPanel.add(productMasterPanel, "products");
contentPanel.add(supplierMasterPanel, "suppliers");
}
private void createSideNavigation() {
sideNavPanel = new JPanel();
sideNavPanel.setLayout(new BoxLayout(sideNavPanel, BoxLayout.Y_AXIS));
sideNavPanel.setBackground(new Color(37, 47, 63));
sideNavPanel.setPreferredSize(new Dimension(220, 0));
sideNavPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
// Navigation title
JLabel titleLabel = new JLabel("QUẢN LÝ KHO");
titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
titleLabel.setForeground(Color.WHITE);
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 0));
sideNavPanel.add(titleLabel);
// Create navigation buttons
dashboardButton = createNavButton("📊 Tổng Quan", "dashboard");
inventoryViewButton = createNavButton("📦 Tồn Kho", "inventory");
goodsReceiptButton = createNavButton("📥 Nhập Kho", "receipt");
stockAdjustmentButton = createNavButton("📋 Kiểm Kê Kho", "adjustment");
reportsButton = createNavButton("📈 Báo Cáo", "reports");
// Add separator
sideNavPanel.add(Box.createVerticalStrut(15));
JLabel masterDataLabel = new JLabel("DANH MỤC");
masterDataLabel.setFont(new Font("Arial", Font.BOLD, 12));
masterDataLabel.setForeground(new Color(150, 150, 150));
masterDataLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
sideNavPanel.add(masterDataLabel);
sideNavPanel.add(Box.createVerticalStrut(10));
productMasterButton = createNavButton("💊 Thuốc", "products");
supplierMasterButton = createNavButton("🏢 Nhà Cung Cấp", "suppliers");
// Add buttons to panel
sideNavPanel.add(dashboardButton);
sideNavPanel.add(inventoryViewButton);
sideNavPanel.add(goodsReceiptButton);
sideNavPanel.add(stockAdjustmentButton);
sideNavPanel.add(reportsButton);
sideNavPanel.add(Box.createVerticalStrut(10));
sideNavPanel.add(productMasterButton);
sideNavPanel.add(supplierMasterButton);
// Add flexible space at bottom
sideNavPanel.add(Box.createVerticalGlue());
}
private JButton createNavButton(String text, String section) {
JButton button = new JButton(text);
button.setFont(new Font("Arial", Font.PLAIN, 14));
button.setForeground(Color.WHITE);
button.setBackground(new Color(37, 47, 63));
button.setBorder(BorderFactory.createEmptyBorder(12, 20, 12, 20));
button.setFocusPainted(false);
button.setAlignmentX(Component.LEFT_ALIGNMENT);
button.setMaximumSize(new Dimension(220, 45));
button.setHorizontalAlignment(SwingConstants.LEFT);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// Hover effect
button.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseEntered(java.awt.event.MouseEvent e) {
if (!section.equals(currentSection)) {
button.setBackground(new Color(52, 62, 78));
}
}
@Override
public void mouseExited(java.awt.event.MouseEvent e) {
if (!section.equals(currentSection)) {
button.setBackground(new Color(37, 47, 63));
}
}
});
return button;
}
private void layoutComponents() {
// Main layout: side navigation + content
add(sideNavPanel, BorderLayout.WEST);
add(contentPanel, BorderLayout.CENTER);
}
private void setupEventListeners() {
dashboardButton.addActionListener(e -> showSection("dashboard"));
inventoryViewButton.addActionListener(e -> showSection("inventory"));
goodsReceiptButton.addActionListener(e -> showSection("receipt"));
stockAdjustmentButton.addActionListener(e -> showSection("adjustment"));
reportsButton.addActionListener(e -> showSection("reports"));
productMasterButton.addActionListener(e -> showSection("products"));
supplierMasterButton.addActionListener(e -> showSection("suppliers"));
}
public void showSection(String section) {
// Update button states
resetNavButtonColors();
currentSection = section;
// Highlight active button
JButton activeButton = getButtonForSection(section);
if (activeButton != null) {
activeButton.setBackground(new Color(63, 81, 181));
}
// Show corresponding panel
cardLayout.show(contentPanel, section);
// Refresh panel data if needed
refreshCurrentPanel(section);
log.info("Switched to inventory section: " + section);
}
private void resetNavButtonColors() {
Color defaultColor = new Color(37, 47, 63);
dashboardButton.setBackground(defaultColor);
inventoryViewButton.setBackground(defaultColor);
goodsReceiptButton.setBackground(defaultColor);
stockAdjustmentButton.setBackground(defaultColor);
reportsButton.setBackground(defaultColor);
productMasterButton.setBackground(defaultColor);
supplierMasterButton.setBackground(defaultColor);
}
private JButton getButtonForSection(String section) {
switch (section) {
case "dashboard": return dashboardButton;
case "inventory": return inventoryViewButton;
case "receipt": return goodsReceiptButton;
case "adjustment": return stockAdjustmentButton;
case "reports": return reportsButton;
case "products": return productMasterButton;
case "suppliers": return supplierMasterButton;
default: return null;
}
}
private void refreshCurrentPanel(String section) {
// TODO: Implement panel refresh logic
switch (section) {
case "dashboard":
dashboardPanel.refreshData();
break;
case "inventory":
inventoryViewPanel.refreshData();
break;
case "receipt":
goodsReceiptPanel.refreshData();
break;
case "adjustment":
stockAdjustmentPanel.refreshData();
break;
case "reports":
reportsPanel.refreshData();
break;
case "products":
productMasterPanel.refreshData();
break;
case "suppliers":
supplierMasterPanel.refreshData();
break;
}
}
// Getters for child panels
public MainFrame getMainFrame() { return mainFrame; }
public NumberFormat getCurrencyFormatter() { return currencyFormatter; }
public SimpleDateFormat getDateFormatter() { return dateFormatter; }
// Navigation methods for child panels
public void navigateToInventoryView() { showSection("inventory"); }
public void navigateToGoodsReceipt() { showSection("receipt"); }
public void navigateToStockAdjustment() { showSection("adjustment"); }
public void navigateToReports() { showSection("reports"); }
public void navigateToProductMaster() { showSection("products"); }
}
@@ -1,437 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import BsK.client.ui.component.InventoryPage.common.InventoryButtonFactory;
import BsK.client.ui.component.InventoryPage.common.InventoryColorScheme;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
public class InventoryViewPanel extends JPanel {
private InventoryPage parentPage;
// Left panel components
private JTextField searchField;
private JComboBox<String> categoryFilter;
private JTable medicineTable;
private DefaultTableModel medicineTableModel;
// Right panel components
private JLabel selectedMedicineLabel;
private JTable batchTable;
private DefaultTableModel batchTableModel;
private JButton adjustStockButton;
private JButton exportButton;
// Current selection
private String selectedMedicineName = "";
private int selectedBatchRow = -1;
// Date formatter
private SimpleDateFormat dateFormatter;
public InventoryViewPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy");
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Search and filter components
searchField = new JTextField();
searchField.setFont(new Font("Arial", Font.PLAIN, 14));
searchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
categoryFilter = new JComboBox<>(new String[]{
"Tất cả danh mục", "Thuốc kê đơn", "Thuốc không kê đơn",
"Vitamin & Thực phẩm chức năng", "Dụng cụ y tế"
});
categoryFilter.setFont(new Font("Arial", Font.PLAIN, 14));
exportButton = InventoryButtonFactory.createSpecialButton("📤 Xuất Excel");
// Medicine table (left panel)
String[] medicineColumns = {"Tên Thuốc", "ĐVT", "Tổng Tồn", "Trạng Thái"};
medicineTableModel = new DefaultTableModel(medicineColumns, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
medicineTable = new JTable(medicineTableModel);
setupMedicineTable();
// Batch table (right panel)
String[] batchColumns = {"Lô", "Hạn Sử Dụng", "Số Lượng", "Giá Nhập", "Ngày Nhập"};
batchTableModel = new DefaultTableModel(batchColumns, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
batchTable = new JTable(batchTableModel);
setupBatchTable();
// Selected medicine label
selectedMedicineLabel = new JLabel("Chọn một loại thuốc để xem chi tiết lô");
selectedMedicineLabel.setFont(new Font("Arial", Font.BOLD, 16));
selectedMedicineLabel.setForeground(new Color(100, 100, 100));
// Adjust stock button
adjustStockButton = InventoryButtonFactory.createSecondaryButton("⚙️ Kiểm Kê");
adjustStockButton.setEnabled(false);
}
private void setupMedicineTable() {
medicineTable.setFont(new Font("Arial", Font.PLAIN, 14));
medicineTable.setRowHeight(35);
medicineTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
medicineTable.setBackground(Color.WHITE);
medicineTable.setGridColor(new Color(230, 230, 230));
medicineTable.setShowVerticalLines(true);
medicineTable.setShowHorizontalLines(true);
medicineTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
medicineTable.getTableHeader().setBackground(InventoryColorScheme.TABLE_HEADER_BG);
medicineTable.getTableHeader().setForeground(InventoryColorScheme.TABLE_HEADER_TEXT);
medicineTable.getTableHeader().setReorderingAllowed(false);
// Set column widths
TableColumn col0 = medicineTable.getColumnModel().getColumn(0); // Tên Thuốc
col0.setPreferredWidth(250);
TableColumn col1 = medicineTable.getColumnModel().getColumn(1); // ĐVT
col1.setPreferredWidth(80);
TableColumn col2 = medicineTable.getColumnModel().getColumn(2); // Tổng Tồn
col2.setPreferredWidth(100);
TableColumn col3 = medicineTable.getColumnModel().getColumn(3); // Trạng Thái
col3.setPreferredWidth(120);
// Custom renderer for status column
medicineTable.getColumnModel().getColumn(3).setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!isSelected) {
String status = (String) value;
c.setBackground(InventoryColorScheme.getStatusBackgroundColor(status));
setForeground(InventoryColorScheme.getStatusColor(status));
}
setHorizontalAlignment(SwingConstants.CENTER);
return c;
}
});
// Center align numeric columns
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
medicineTable.getColumnModel().getColumn(1).setCellRenderer(centerRenderer);
medicineTable.getColumnModel().getColumn(2).setCellRenderer(centerRenderer);
}
private void setupBatchTable() {
batchTable.setFont(new Font("Arial", Font.PLAIN, 14));
batchTable.setRowHeight(35);
batchTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
batchTable.setBackground(Color.WHITE);
batchTable.setGridColor(new Color(230, 230, 230));
batchTable.setShowVerticalLines(true);
batchTable.setShowHorizontalLines(true);
batchTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
batchTable.getTableHeader().setBackground(InventoryColorScheme.TABLE_HEADER_BG);
batchTable.getTableHeader().setForeground(InventoryColorScheme.TABLE_HEADER_TEXT);
batchTable.getTableHeader().setReorderingAllowed(false);
// Set column widths
TableColumn col0 = batchTable.getColumnModel().getColumn(0); // Lô
col0.setPreferredWidth(120);
TableColumn col1 = batchTable.getColumnModel().getColumn(1); // HSD
col1.setPreferredWidth(100);
TableColumn col2 = batchTable.getColumnModel().getColumn(2); // Số Lượng
col2.setPreferredWidth(100);
TableColumn col3 = batchTable.getColumnModel().getColumn(3); // Giá Nhập
col3.setPreferredWidth(120);
TableColumn col4 = batchTable.getColumnModel().getColumn(4); // Ngày Nhập
col4.setPreferredWidth(100);
// Center align all columns
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
for (int i = 0; i < batchTable.getColumnCount(); i++) {
batchTable.getColumnModel().getColumn(i).setCellRenderer(centerRenderer);
}
// Right align price column
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
batchTable.getColumnModel().getColumn(3).setCellRenderer(rightRenderer);
}
private void layoutComponents() {
// Top panel with search and filters
JPanel topPanel = createTopPanel();
add(topPanel, BorderLayout.NORTH);
// Main content: split pane with medicine list and batch details
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setDividerLocation(550);
splitPane.setResizeWeight(0.55);
splitPane.setBorder(null);
// Left panel: Medicine list
JPanel leftPanel = createMedicineListPanel();
splitPane.setLeftComponent(leftPanel);
// Right panel: Batch details
JPanel rightPanel = createBatchDetailsPanel();
splitPane.setRightComponent(rightPanel);
add(splitPane, BorderLayout.CENTER);
}
private JPanel createTopPanel() {
RoundedPanel topPanel = new RoundedPanel(10, Color.WHITE, true);
topPanel.setLayout(new BorderLayout(15, 0));
topPanel.setBorder(BorderFactory.createEmptyBorder(20, 25, 20, 25));
// Left side: Search and filter
JPanel leftControls = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));
leftControls.setOpaque(false);
// Search field with placeholder effect
JPanel searchPanel = new JPanel(new BorderLayout());
searchPanel.setOpaque(false);
searchPanel.setPreferredSize(new Dimension(300, 35));
JLabel searchIcon = new JLabel("🔍");
searchIcon.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
searchPanel.add(searchIcon, BorderLayout.WEST);
searchPanel.add(searchField, BorderLayout.CENTER);
categoryFilter.setPreferredSize(new Dimension(200, 35));
leftControls.add(new JLabel("Tìm kiếm:"));
leftControls.add(searchPanel);
leftControls.add(new JLabel("Danh mục:"));
leftControls.add(categoryFilter);
// Right side: Grouped action buttons
JButton printReportButton = InventoryButtonFactory.createSecondaryButton("📊 In Báo Cáo");
JPanel rightControls = InventoryButtonFactory.createButtonGroup(FlowLayout.RIGHT, exportButton, printReportButton);
topPanel.add(leftControls, BorderLayout.WEST);
topPanel.add(rightControls, BorderLayout.EAST);
return topPanel;
}
private JPanel createMedicineListPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title
JLabel titleLabel = new JLabel("DANH SÁCH THUỐC TRONG KHO");
titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
titleLabel.setForeground(new Color(37, 47, 63));
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0));
panel.add(titleLabel, BorderLayout.NORTH);
// Table with scroll
JScrollPane scrollPane = new JScrollPane(medicineTable);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200)));
scrollPane.getViewport().setBackground(Color.WHITE);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private JPanel createBatchDetailsPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Header with medicine name and actions
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.setOpaque(false);
headerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0));
selectedMedicineLabel.setText("CHI TIẾT LÔ THUỐC");
headerPanel.add(selectedMedicineLabel, BorderLayout.WEST);
headerPanel.add(adjustStockButton, BorderLayout.EAST);
panel.add(headerPanel, BorderLayout.NORTH);
// Table with scroll
JScrollPane scrollPane = new JScrollPane(batchTable);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200)));
scrollPane.getViewport().setBackground(Color.WHITE);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private void setupEventListeners() {
// Search field
searchField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
filterMedicineList();
}
});
// Category filter
categoryFilter.addActionListener(e -> filterMedicineList());
// Medicine table selection
medicineTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
onMedicineSelected();
}
}
});
// Batch table selection
batchTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
onBatchSelected();
}
}
});
// Export button
exportButton.addActionListener(e -> exportToExcel());
// Adjust stock button
adjustStockButton.addActionListener(e -> openStockAdjustment());
}
private void filterMedicineList() {
// TODO: Implement filtering logic
log.info("Filtering medicine list with search: '" + searchField.getText() +
"' and category: '" + categoryFilter.getSelectedItem() + "'");
}
private void onMedicineSelected() {
int selectedRow = medicineTable.getSelectedRow();
if (selectedRow >= 0) {
selectedMedicineName = (String) medicineTableModel.getValueAt(selectedRow, 0);
selectedMedicineLabel.setText("CHI TIẾT LÔ: " + selectedMedicineName);
loadBatchDetails(selectedMedicineName);
adjustStockButton.setEnabled(false); // Will be enabled when batch is selected
}
}
private void onBatchSelected() {
selectedBatchRow = batchTable.getSelectedRow();
adjustStockButton.setEnabled(selectedBatchRow >= 0);
}
private void loadBatchDetails(String medicineName) {
// Clear existing data
batchTableModel.setRowCount(0);
// TODO: Load actual batch data from database
loadMockBatchData(medicineName);
}
private void loadMockBatchData(String medicineName) {
// Mock batch data
Object[][] batchData = {
{medicineName.substring(0, 2).toUpperCase() + "202408", "15/08/2025", "45", "52.000", "10/01/2024"},
{medicineName.substring(0, 2).toUpperCase() + "202411", "30/11/2025", "18", "55.000", "15/03/2024"},
{medicineName.substring(0, 2).toUpperCase() + "202501", "15/02/2026", "30", "58.000", "20/05/2024"}
};
for (Object[] row : batchData) {
batchTableModel.addRow(row);
}
log.info("Loaded batch details for: " + medicineName);
}
private void exportToExcel() {
// TODO: Implement Excel export functionality
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Xuất danh sách tồn kho");
fileChooser.setSelectedFile(new File("TonKho_" + dateFormatter.format(new Date()) + ".xlsx"));
int result = fileChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
// TODO: Export logic here
JOptionPane.showMessageDialog(this,
"Xuất file thành công!\nĐường dẫn: " + selectedFile.getAbsolutePath(),
"Thông báo", JOptionPane.INFORMATION_MESSAGE);
}
}
private void openStockAdjustment() {
if (selectedBatchRow >= 0) {
String batchNumber = (String) batchTableModel.getValueAt(selectedBatchRow, 0);
String currentQuantity = (String) batchTableModel.getValueAt(selectedBatchRow, 2);
// TODO: Open stock adjustment dialog
log.info("Opening stock adjustment for batch: " + batchNumber + " (current: " + currentQuantity + ")");
parentPage.navigateToStockAdjustment();
}
}
public void refreshData() {
// TODO: Load actual data from database
loadMockMedicineData();
}
private void loadMockMedicineData() {
// Clear existing data
medicineTableModel.setRowCount(0);
// Mock medicine data with enhanced status showing quantity ratios
Object[][] medicineData = {
{"Paracetamol 500mg", "Hộp", "63", "63/20 - Bình thường"},
{"Amoxicillin 250mg", "Hộp", "112", "112/30 - Bình thường"},
{"Vitamin C 1000mg", "Chai", "8", "8/20 - Sắp hết"},
{"Ibuprofen 400mg", "Hộp", "45", "45/25 - Bình thường"},
{"Aspirin 100mg", "Hộp", "5", "5/15 - Sắp hết"},
{"Omeprazole 20mg", "Hộp", "78", "78/30 - Bình thường"},
{"Metformin 500mg", "Hộp", "92", "92/40 - Bình thường"},
{"Atorvastatin 20mg", "Hộp", "34", "34/25 - Bình thường"},
{"Amlodipine 5mg", "Hộp", "67", "67/30 - Bình thường"},
{"Losartan 50mg", "Hộp", "12", "12/25 - Sắp hết"}
};
for (Object[] row : medicineData) {
medicineTableModel.addRow(row);
}
log.info("Medicine inventory data refreshed - " + medicineData.length + " items loaded");
}
}
@@ -1,526 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@Slf4j
public class ProductMasterPanel extends JPanel {
private InventoryPage parentPage;
// Product list
private JTextField searchField;
private JTable productTable;
private DefaultTableModel productTableModel;
// Product form
private JTextField nameField;
private JTextField activeIngredientField;
private JComboBox<String> categoryCombo;
private JTextField manufacturerField;
private JTextField descriptionField;
// Units and pricing
private JTextField unit1Field, unit2Field, unit3Field;
private JTextField conversion1Field, conversion2Field;
private JTextField price1Field, price2Field, price3Field;
private JSpinner minStockSpinner;
// Actions
private JButton addButton;
private JButton editButton;
private JButton saveButton;
private JButton cancelButton;
private JButton deleteButton;
private boolean isEditing = false;
private int editingRow = -1;
public ProductMasterPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Search field
searchField = new JTextField();
searchField.setFont(new Font("Arial", Font.PLAIN, 14));
searchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
// Product table
String[] columns = {"Tên Thuốc", "Hoạt Chất", "Danh Mục", "ĐVT Chính", "Giá Bán", "Tồn Tối Thiểu"};
productTableModel = new DefaultTableModel(columns, 0) {
@Override
public boolean isCellEditable(int row, int column) { return false; }
};
productTable = new JTable(productTableModel);
setupProductTable();
// Form fields
nameField = createFormField();
activeIngredientField = createFormField();
manufacturerField = createFormField();
descriptionField = createFormField();
categoryCombo = new JComboBox<>(new String[]{
"Thuốc kê đơn", "Thuốc không kê đơn", "Vitamin & TPCN",
"Thuốc tiêm", "Dụng cụ y tế", "Khác"
});
categoryCombo.setFont(new Font("Arial", Font.PLAIN, 14));
// Units and conversions
unit1Field = createFormField(); // Main unit
unit2Field = createFormField(); // Secondary unit
unit3Field = createFormField(); // Tertiary unit
conversion1Field = createFormField(); // 1 unit1 = ? unit2
conversion2Field = createFormField(); // 1 unit2 = ? unit3
// Pricing
price1Field = createFormField();
price2Field = createFormField();
price3Field = createFormField();
minStockSpinner = new JSpinner(new SpinnerNumberModel(10, 0, 9999, 1));
minStockSpinner.setFont(new Font("Arial", Font.PLAIN, 14));
// Action buttons
addButton = createActionButton("➕ Thêm Mới", new Color(76, 175, 80));
editButton = createActionButton("✏️ Sửa", new Color(255, 152, 0));
saveButton = createActionButton("💾 Lưu", new Color(33, 150, 243));
cancelButton = createActionButton("❌ Hủy", new Color(158, 158, 158));
deleteButton = createActionButton("🗑️ Xóa", new Color(244, 67, 54));
saveButton.setEnabled(false);
cancelButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
}
private JTextField createFormField() {
JTextField field = new JTextField();
field.setFont(new Font("Arial", Font.PLAIN, 14));
field.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
field.setEnabled(false);
return field;
}
private JButton createActionButton(String text, Color color) {
JButton button = new JButton(text);
button.setFont(new Font("Arial", Font.BOLD, 14));
button.setBackground(color);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
private void setupProductTable() {
productTable.setFont(new Font("Arial", Font.PLAIN, 14));
productTable.setRowHeight(35);
productTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
productTable.setBackground(Color.WHITE);
productTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
productTable.getTableHeader().setBackground(new Color(37, 47, 63));
productTable.getTableHeader().setForeground(Color.WHITE);
// Right align price column
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
productTable.getColumnModel().getColumn(4).setCellRenderer(rightRenderer);
// Center align numeric columns
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
productTable.getColumnModel().getColumn(3).setCellRenderer(centerRenderer);
productTable.getColumnModel().getColumn(5).setCellRenderer(centerRenderer);
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("QUẢN LÝ DANH MỤC THUỐC", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Split pane: list + form
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setDividerLocation(600);
splitPane.setResizeWeight(0.6);
// Left: Product list
RoundedPanel listPanel = createProductListPanel();
splitPane.setLeftComponent(listPanel);
// Right: Product form
RoundedPanel formPanel = createProductFormPanel();
splitPane.setRightComponent(formPanel);
mainPanel.add(splitPane, BorderLayout.CENTER);
add(mainPanel, BorderLayout.CENTER);
}
private RoundedPanel createProductListPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Header with search
JPanel headerPanel = new JPanel(new BorderLayout(10, 0));
headerPanel.setOpaque(false);
JLabel headerLabel = new JLabel("DANH SÁCH THUỐC");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
JPanel searchPanel = new JPanel(new BorderLayout(5, 0));
searchPanel.setOpaque(false);
searchPanel.add(new JLabel("🔍"), BorderLayout.WEST);
searchPanel.add(searchField, BorderLayout.CENTER);
searchPanel.setPreferredSize(new Dimension(250, 35));
headerPanel.add(headerLabel, BorderLayout.WEST);
headerPanel.add(searchPanel, BorderLayout.EAST);
panel.add(headerPanel, BorderLayout.NORTH);
// Table
JScrollPane scrollPane = new JScrollPane(productTable);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200)));
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private RoundedPanel createProductFormPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Header
JLabel headerLabel = new JLabel("THÔNG TIN THUỐC");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, BorderLayout.NORTH);
// Form fields
JPanel formPanel = new JPanel(new GridBagLayout());
formPanel.setOpaque(false);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 5, 8, 10);
gbc.anchor = GridBagConstraints.WEST;
// Basic info
addFormRow(formPanel, gbc, 0, "Tên thuốc:", nameField);
addFormRow(formPanel, gbc, 1, "Hoạt chất:", activeIngredientField);
addFormRow(formPanel, gbc, 2, "Danh mục:", categoryCombo);
addFormRow(formPanel, gbc, 3, "Nhà sản xuất:", manufacturerField);
addFormRow(formPanel, gbc, 4, "Mô tả:", descriptionField);
// Separator
gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
JSeparator separator1 = new JSeparator();
separator1.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
formPanel.add(separator1, gbc);
// Units section
gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE;
JLabel unitsLabel = new JLabel("ĐƠNN VỊ & QUY ĐỔI");
unitsLabel.setFont(new Font("Arial", Font.BOLD, 14));
unitsLabel.setForeground(new Color(37, 47, 63));
gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2;
formPanel.add(unitsLabel, gbc);
gbc.gridwidth = 1;
addFormRow(formPanel, gbc, 7, "ĐVT chính:", unit1Field);
addFormRow(formPanel, gbc, 8, "ĐVT phụ:", unit2Field);
addFormRow(formPanel, gbc, 9, "ĐVT nhỏ:", unit3Field);
addFormRow(formPanel, gbc, 10, "1 chính = ? phụ:", conversion1Field);
addFormRow(formPanel, gbc, 11, "1 phụ = ? nhỏ:", conversion2Field);
// Separator
gbc.gridx = 0; gbc.gridy = 12; gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
JSeparator separator2 = new JSeparator();
separator2.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
formPanel.add(separator2, gbc);
// Pricing section
gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE;
JLabel pricingLabel = new JLabel("GIÁ BÁN & TỒN KHO");
pricingLabel.setFont(new Font("Arial", Font.BOLD, 14));
pricingLabel.setForeground(new Color(37, 47, 63));
gbc.gridx = 0; gbc.gridy = 13; gbc.gridwidth = 2;
formPanel.add(pricingLabel, gbc);
gbc.gridwidth = 1;
addFormRow(formPanel, gbc, 14, "Giá bán chính:", price1Field);
addFormRow(formPanel, gbc, 15, "Giá bán phụ:", price2Field);
addFormRow(formPanel, gbc, 16, "Giá bán nhỏ:", price3Field);
addFormRow(formPanel, gbc, 17, "Tồn tối thiểu:", minStockSpinner);
panel.add(formPanel, BorderLayout.CENTER);
// Action buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
buttonPanel.setOpaque(false);
buttonPanel.add(addButton);
buttonPanel.add(editButton);
buttonPanel.add(saveButton);
buttonPanel.add(cancelButton);
buttonPanel.add(deleteButton);
panel.add(buttonPanel, BorderLayout.SOUTH);
return panel;
}
private void addFormRow(JPanel parent, GridBagConstraints gbc, int row, String label, JComponent component) {
gbc.gridx = 0; gbc.gridy = row;
parent.add(new JLabel(label), gbc);
gbc.gridx = 1;
component.setPreferredSize(new Dimension(200, 30));
parent.add(component, gbc);
}
private void setupEventListeners() {
// Table selection
productTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
onProductSelected();
}
});
// Action buttons
addButton.addActionListener(e -> startAdd());
editButton.addActionListener(e -> startEdit());
saveButton.addActionListener(e -> saveProduct());
cancelButton.addActionListener(e -> cancelOperation());
deleteButton.addActionListener(e -> deleteProduct());
}
private void onProductSelected() {
int row = productTable.getSelectedRow();
boolean hasSelection = row >= 0;
editButton.setEnabled(hasSelection && !isEditing);
deleteButton.setEnabled(hasSelection && !isEditing);
if (hasSelection) {
loadProductToForm(row);
}
}
private void loadProductToForm(int row) {
// TODO: Load actual product data
nameField.setText((String) productTableModel.getValueAt(row, 0));
activeIngredientField.setText("Paracetamol"); // Mock data
categoryCombo.setSelectedItem(productTableModel.getValueAt(row, 2));
manufacturerField.setText("Công ty ABC");
descriptionField.setText("Thuốc giảm đau, hạ sốt");
unit1Field.setText("Hộp");
unit2Field.setText("Vỉ");
unit3Field.setText("Viên");
conversion1Field.setText("10");
conversion2Field.setText("10");
price1Field.setText("100000");
price2Field.setText("10000");
price3Field.setText("1000");
minStockSpinner.setValue(20);
}
private void startAdd() {
isEditing = true;
editingRow = -1;
enableFormFields(true);
clearForm();
addButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
saveButton.setEnabled(true);
cancelButton.setEnabled(true);
}
private void startEdit() {
int row = productTable.getSelectedRow();
if (row < 0) return;
isEditing = true;
editingRow = row;
enableFormFields(true);
addButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
saveButton.setEnabled(true);
cancelButton.setEnabled(true);
}
private void saveProduct() {
if (!validateForm()) return;
Object[] rowData = {
nameField.getText(),
activeIngredientField.getText(),
categoryCombo.getSelectedItem(),
unit1Field.getText(),
parentPage.getCurrencyFormatter().format(Double.parseDouble(price1Field.getText())),
minStockSpinner.getValue().toString()
};
if (editingRow >= 0) {
// Update existing
for (int i = 0; i < rowData.length; i++) {
productTableModel.setValueAt(rowData[i], editingRow, i);
}
JOptionPane.showMessageDialog(this, "Cập nhật thuốc thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
} else {
// Add new
productTableModel.addRow(rowData);
JOptionPane.showMessageDialog(this, "Thêm thuốc mới thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
}
// TODO: Save to database
cancelOperation();
}
private void cancelOperation() {
isEditing = false;
editingRow = -1;
enableFormFields(false);
addButton.setEnabled(true);
saveButton.setEnabled(false);
cancelButton.setEnabled(false);
onProductSelected(); // Refresh button states
}
private void deleteProduct() {
int row = productTable.getSelectedRow();
if (row < 0) return;
String productName = (String) productTableModel.getValueAt(row, 0);
int confirm = JOptionPane.showConfirmDialog(this,
"Bạn có chắc chắn muốn xóa thuốc '" + productName + "'?",
"Xác nhận xóa", JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
productTableModel.removeRow(row);
clearForm();
JOptionPane.showMessageDialog(this, "Xóa thuốc thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
// TODO: Delete from database
}
}
private void enableFormFields(boolean enabled) {
nameField.setEnabled(enabled);
activeIngredientField.setEnabled(enabled);
categoryCombo.setEnabled(enabled);
manufacturerField.setEnabled(enabled);
descriptionField.setEnabled(enabled);
unit1Field.setEnabled(enabled);
unit2Field.setEnabled(enabled);
unit3Field.setEnabled(enabled);
conversion1Field.setEnabled(enabled);
conversion2Field.setEnabled(enabled);
price1Field.setEnabled(enabled);
price2Field.setEnabled(enabled);
price3Field.setEnabled(enabled);
minStockSpinner.setEnabled(enabled);
}
private void clearForm() {
nameField.setText("");
activeIngredientField.setText("");
categoryCombo.setSelectedIndex(0);
manufacturerField.setText("");
descriptionField.setText("");
unit1Field.setText("");
unit2Field.setText("");
unit3Field.setText("");
conversion1Field.setText("");
conversion2Field.setText("");
price1Field.setText("");
price2Field.setText("");
price3Field.setText("");
minStockSpinner.setValue(10);
}
private boolean validateForm() {
if (nameField.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập tên thuốc!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
nameField.requestFocus();
return false;
}
if (unit1Field.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập đơn vị chính!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
unit1Field.requestFocus();
return false;
}
try {
Double.parseDouble(price1Field.getText());
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập giá bán hợp lệ!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
price1Field.requestFocus();
return false;
}
return true;
}
public void refreshData() {
// TODO: Load actual data from database
loadMockProductData();
}
private void loadMockProductData() {
productTableModel.setRowCount(0);
Object[][] mockData = {
{"Paracetamol 500mg", "Paracetamol", "Thuốc không kê đơn", "Hộp", "100.000", "20"},
{"Amoxicillin 250mg", "Amoxicillin", "Thuốc kê đơn", "Hộp", "150.000", "15"},
{"Vitamin C 1000mg", "Acid Ascorbic", "Vitamin & TPCN", "Chai", "85.000", "10"},
{"Ibuprofen 400mg", "Ibuprofen", "Thuốc không kê đơn", "Hộp", "120.000", "25"},
{"Omeprazole 20mg", "Omeprazole", "Thuốc kê đơn", "Hộp", "200.000", "30"}
};
for (Object[] row : mockData) {
productTableModel.addRow(row);
}
log.info("Product master data loaded - " + mockData.length + " products");
}
}
@@ -1,473 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
public class ReportsPanel extends JPanel {
private InventoryPage parentPage;
// Report type selection
private JComboBox<String> reportTypeCombo;
private JButton generateButton;
private JButton exportButton;
// Report results
private JTable reportTable;
private DefaultTableModel reportTableModel;
private JLabel reportTitleLabel;
private JLabel reportSummaryLabel;
// Date filter (for some reports)
private JTextField fromDateField;
private JTextField toDateField;
private SimpleDateFormat dateFormatter;
public ReportsPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy");
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Report type selection
reportTypeCombo = new JComboBox<>(new String[]{
"Chọn loại báo cáo...",
"Thuốc Sắp Hết (Low Stock)",
"Thuốc Sắp Hết Hạn",
"Lịch Sử Xuất Nhập Kho",
"Tồn Kho Theo Nhóm",
"Báo Cáo Tổng Hợp"
});
reportTypeCombo.setFont(new Font("Arial", Font.PLAIN, 14));
reportTypeCombo.setPreferredSize(new Dimension(250, 35));
generateButton = new JButton("📊 Tạo Báo Cáo");
generateButton.setFont(new Font("Arial", Font.BOLD, 14));
generateButton.setBackground(new Color(33, 150, 243));
generateButton.setForeground(Color.WHITE);
generateButton.setFocusPainted(false);
generateButton.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
generateButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
exportButton = new JButton("📤 Xuất Excel");
exportButton.setFont(new Font("Arial", Font.BOLD, 14));
exportButton.setBackground(new Color(76, 175, 80));
exportButton.setForeground(Color.WHITE);
exportButton.setFocusPainted(false);
exportButton.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
exportButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
exportButton.setEnabled(false);
// Date filters
fromDateField = new JTextField(dateFormatter.format(new Date()));
fromDateField.setFont(new Font("Arial", Font.PLAIN, 14));
fromDateField.setPreferredSize(new Dimension(120, 35));
fromDateField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
toDateField = new JTextField(dateFormatter.format(new Date()));
toDateField.setFont(new Font("Arial", Font.PLAIN, 14));
toDateField.setPreferredSize(new Dimension(120, 35));
toDateField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
// Report results
reportTableModel = new DefaultTableModel();
reportTable = new JTable(reportTableModel);
setupReportTable();
reportTitleLabel = new JLabel("Chọn loại báo cáo để hiển thị kết quả");
reportTitleLabel.setFont(new Font("Arial", Font.BOLD, 18));
reportTitleLabel.setForeground(new Color(37, 47, 63));
reportSummaryLabel = new JLabel("");
reportSummaryLabel.setFont(new Font("Arial", Font.PLAIN, 14));
reportSummaryLabel.setForeground(new Color(100, 100, 100));
}
private void setupReportTable() {
reportTable.setFont(new Font("Arial", Font.PLAIN, 14));
reportTable.setRowHeight(35);
reportTable.setBackground(Color.WHITE);
reportTable.setGridColor(new Color(230, 230, 230));
reportTable.setShowVerticalLines(true);
reportTable.setShowHorizontalLines(true);
reportTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
reportTable.getTableHeader().setBackground(new Color(37, 47, 63));
reportTable.getTableHeader().setForeground(Color.WHITE);
reportTable.getTableHeader().setReorderingAllowed(false);
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("BÁO CÁO QUẢN LÝ KHO", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Controls panel
RoundedPanel controlsPanel = createControlsPanel();
mainPanel.add(controlsPanel, BorderLayout.CENTER);
// Results panel
RoundedPanel resultsPanel = createResultsPanel();
mainPanel.add(resultsPanel, BorderLayout.SOUTH);
add(mainPanel, BorderLayout.CENTER);
}
private RoundedPanel createControlsPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
panel.setPreferredSize(new Dimension(0, 200));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.anchor = GridBagConstraints.WEST;
// Report type selection
gbc.gridx = 0; gbc.gridy = 0;
panel.add(new JLabel("Loại báo cáo:"), gbc);
gbc.gridx = 1; gbc.gridwidth = 2;
panel.add(reportTypeCombo, gbc);
// Date range (initially hidden)
gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1;
JLabel fromLabel = new JLabel("Từ ngày:");
panel.add(fromLabel, gbc);
gbc.gridx = 1;
panel.add(fromDateField, gbc);
gbc.gridx = 2;
JLabel toLabel = new JLabel("Đến ngày:");
panel.add(toLabel, gbc);
gbc.gridx = 3;
panel.add(toDateField, gbc);
// Buttons
gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2;
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));
buttonPanel.setOpaque(false);
buttonPanel.add(generateButton);
buttonPanel.add(exportButton);
panel.add(buttonPanel, gbc);
return panel;
}
private RoundedPanel createResultsPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
panel.setPreferredSize(new Dimension(0, 400));
// Header
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.setOpaque(false);
headerPanel.add(reportTitleLabel, BorderLayout.WEST);
headerPanel.add(reportSummaryLabel, BorderLayout.EAST);
panel.add(headerPanel, BorderLayout.NORTH);
// Table
JScrollPane scrollPane = new JScrollPane(reportTable);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200)));
scrollPane.getViewport().setBackground(Color.WHITE);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private void setupEventListeners() {
generateButton.addActionListener(e -> generateReport());
exportButton.addActionListener(e -> exportReport());
reportTypeCombo.addActionListener(e -> {
String selectedType = (String) reportTypeCombo.getSelectedItem();
boolean needsDateRange = "Lịch Sử Xuất Nhập Kho".equals(selectedType);
fromDateField.setVisible(needsDateRange);
toDateField.setVisible(needsDateRange);
});
}
private void generateReport() {
String reportType = (String) reportTypeCombo.getSelectedItem();
if (reportType == null || reportType.startsWith("Chọn")) {
JOptionPane.showMessageDialog(this, "Vui lòng chọn loại báo cáo!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
return;
}
reportTitleLabel.setText("Đang tạo báo cáo...");
reportSummaryLabel.setText("");
reportTableModel.setRowCount(0);
reportTableModel.setColumnCount(0);
// Generate report based on type
switch (reportType) {
case "Thuốc Sắp Hết (Low Stock)":
generateLowStockReport();
break;
case "Thuốc Sắp Hết Hạn":
generateExpiryReport();
break;
case "Lịch Sử Xuất Nhập Kho":
generateInventoryHistoryReport();
break;
case "Tồn Kho Theo Nhóm":
generateStockByGroupReport();
break;
case "Báo Cáo Tổng Hợp":
generateSummaryReport();
break;
}
exportButton.setEnabled(reportTableModel.getRowCount() > 0);
}
private void generateLowStockReport() {
String[] columns = {"Tên Thuốc", "ĐVT", "Tồn Hiện Tại", "Mức Tối Thiểu", "Cần Nhập", "Nhà Cung Cấp"};
reportTableModel.setColumnIdentifiers(columns);
// Mock data
Object[][] data = {
{"Vitamin C 1000mg", "Chai", "8", "20", "12", "Công ty ABC"},
{"Aspirin 100mg", "Hộp", "5", "15", "10", "Công ty DEF"},
{"Cefuroxime 250mg", "Hộp", "3", "10", "7", "Công ty GHI"},
{"Omeprazole 20mg", "Hộp", "12", "25", "13", "Công ty ABC"},
{"Metformin 500mg", "Hộp", "18", "30", "12", "Công ty JKL"}
};
for (Object[] row : data) {
reportTableModel.addRow(row);
}
reportTitleLabel.setText("Báo Cáo Thuốc Sắp Hết");
reportSummaryLabel.setText(data.length + " sản phẩm cần nhập thêm");
// Color coding for urgent items
reportTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!isSelected && column == 2) { // Current stock column
try {
int current = Integer.parseInt(value.toString());
if (current <= 5) {
c.setBackground(new Color(255, 235, 238));
setForeground(new Color(244, 67, 54));
} else if (current <= 10) {
c.setBackground(new Color(255, 248, 225));
setForeground(new Color(255, 152, 0));
} else {
c.setBackground(Color.WHITE);
setForeground(Color.BLACK);
}
} catch (NumberFormatException e) {
c.setBackground(Color.WHITE);
setForeground(Color.BLACK);
}
} else if (!isSelected) {
c.setBackground(Color.WHITE);
setForeground(Color.BLACK);
}
setHorizontalAlignment(column >= 1 && column <= 4 ? SwingConstants.CENTER : SwingConstants.LEFT);
return c;
}
});
log.info("Generated low stock report with " + data.length + " items");
}
private void generateExpiryReport() {
String[] columns = {"Tên Thuốc", "Lô", "Hạn Sử Dụng", "Số Lượng", "Ngày Còn Lại", "Mức Độ"};
reportTableModel.setColumnIdentifiers(columns);
// Mock data
Object[][] data = {
{"Paracetamol 500mg", "PA202408", "15/08/2024", "25", "45", "Nguy hiểm"},
{"Vitamin B1", "VB202409", "20/09/2024", "15", "78", "Cảnh báo"},
{"Amoxicillin 250mg", "AM202410", "10/10/2024", "30", "98", "Cảnh báo"},
{"Ibuprofen 400mg", "IB202411", "05/11/2024", "18", "124", "Bình thường"},
{"Cefalexin 500mg", "CE202412", "25/12/2024", "22", "174", "Bình thường"}
};
for (Object[] row : data) {
reportTableModel.addRow(row);
}
reportTitleLabel.setText("Báo Cáo Thuốc Sắp Hết Hạn");
reportSummaryLabel.setText(data.length + " lô thuốc cần theo dõi");
// Color coding for expiry urgency
reportTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!isSelected && column == 5) { // Risk level column
String risk = value.toString();
if ("Nguy hiểm".equals(risk)) {
c.setBackground(new Color(255, 235, 238));
setForeground(new Color(244, 67, 54));
} else if ("Cảnh báo".equals(risk)) {
c.setBackground(new Color(255, 248, 225));
setForeground(new Color(255, 152, 0));
} else {
c.setBackground(new Color(232, 245, 233));
setForeground(new Color(76, 175, 80));
}
} else if (!isSelected) {
c.setBackground(Color.WHITE);
setForeground(Color.BLACK);
}
setHorizontalAlignment(column >= 3 && column <= 4 ? SwingConstants.CENTER : SwingConstants.LEFT);
return c;
}
});
log.info("Generated expiry report with " + data.length + " batches");
}
private void generateInventoryHistoryReport() {
String[] columns = {"Ngày", "Loại", "Thuốc", "Lô", "Số Lượng", "Đơn Giá", "Người Thực Hiện"};
reportTableModel.setColumnIdentifiers(columns);
// Mock data
Object[][] data = {
{"25/06/2024", "Nhập", "Paracetamol 500mg", "PA202408", "+100", "52.000", "Nguyễn Văn A"},
{"25/06/2024", "Xuất", "Vitamin C 1000mg", "VC202405", "-5", "85.000", "Hệ thống"},
{"24/06/2024", "Kiểm kê", "Amoxicillin 250mg", "AM202409", "+12", "-", "Trần Thị B"},
{"24/06/2024", "Xuất", "Ibuprofen 400mg", "IB202407", "-8", "45.000", "Hệ thống"},
{"23/06/2024", "Nhập", "Omeprazole 20mg", "OM202410", "+50", "38.000", "Lê Văn C"}
};
for (Object[] row : data) {
reportTableModel.addRow(row);
}
reportTitleLabel.setText("Lịch Sử Xuất Nhập Kho");
reportSummaryLabel.setText("Từ " + fromDateField.getText() + " đến " + toDateField.getText());
log.info("Generated inventory history report with " + data.length + " transactions");
}
private void generateStockByGroupReport() {
String[] columns = {"Nhóm Thuốc", "Số Loại", "Tổng Tồn", "Giá Trị (VNĐ)", "% Tổng Kho"};
reportTableModel.setColumnIdentifiers(columns);
// Mock data
Object[][] data = {
{"Thuốc kê đơn", "45", "2,847", "156,890,000", "34.5%"},
{"Thuốc không kê đơn", "32", "1,923", "98,450,000", "21.6%"},
{"Vitamin & TPCN", "28", "1,456", "76,230,000", "16.7%"},
{"Thuốc tiêm", "15", "578", "89,670,000", "19.7%"},
{"Dụng cụ y tế", "12", "234", "33,760,000", "7.4%"}
};
for (Object[] row : data) {
reportTableModel.addRow(row);
}
reportTitleLabel.setText("Tồn Kho Theo Nhóm Thuốc");
reportSummaryLabel.setText("Tổng: 132 loại thuốc, giá trị 455.000.000 VNĐ");
// Right align numeric columns
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
for (int i = 1; i < reportTable.getColumnCount(); i++) {
reportTable.getColumnModel().getColumn(i).setCellRenderer(rightRenderer);
}
log.info("Generated stock by group report");
}
private void generateSummaryReport() {
String[] columns = {"Chỉ Số", "Giá Trị", "Đơn Vị", "Ghi Chú"};
reportTableModel.setColumnIdentifiers(columns);
// Mock summary data
Object[][] data = {
{"Tổng số loại thuốc", "156", "loại", "Đang quản lý"},
{"Tổng giá trị kho", "455.000.000", "VNĐ", "Tại thời điểm hiện tại"},
{"Thuốc sắp hết", "7", "loại", "Dưới mức tối thiểu"},
{"Thuốc sắp hết hạn", "12", "lô", "Trong 3 tháng tới"},
{"Giao dịch hôm nay", "23", "lần", "Xuất + Nhập"},
{"Doanh thu thuốc", "12.450.000", "VNĐ", "Tháng này"},
{"Chi phí nhập hàng", "8.900.000", "VNĐ", "Tháng này"},
{"Tỷ lệ hết hạn", "0.8%", "%", "Tháng trước"}
};
for (Object[] row : data) {
reportTableModel.addRow(row);
}
reportTitleLabel.setText("Báo Cáo Tổng Hợp Kho Thuốc");
reportSummaryLabel.setText("Cập nhật lúc: " + new SimpleDateFormat("dd/MM/yyyy HH:mm").format(new Date()));
log.info("Generated summary report");
}
private void exportReport() {
String reportType = (String) reportTypeCombo.getSelectedItem();
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Xuất báo cáo Excel");
String fileName = reportType.replaceAll("[^a-zA-Z0-9]", "_") + "_" +
dateFormatter.format(new Date()) + ".xlsx";
fileChooser.setSelectedFile(new File(fileName));
int result = fileChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
// TODO: Implement actual Excel export
JOptionPane.showMessageDialog(this,
"Xuất báo cáo thành công!\nFile: " + selectedFile.getName() +
"\nSố dòng: " + reportTableModel.getRowCount(),
"Thông báo", JOptionPane.INFORMATION_MESSAGE);
log.info("Exported report: " + selectedFile.getAbsolutePath());
}
}
public void refreshData() {
reportTitleLabel.setText("Chọn loại báo cáo để hiển thị kết quả");
reportSummaryLabel.setText("");
reportTableModel.setRowCount(0);
reportTableModel.setColumnCount(0);
exportButton.setEnabled(false);
log.info("Reports panel refreshed");
}
}
@@ -1,498 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
public class StockAdjustmentPanel extends JPanel {
private InventoryPage parentPage;
// Search and selection
private JTextField medicineSearchField;
private JTable medicineTable;
private DefaultTableModel medicineTableModel;
private JTable batchTable;
private DefaultTableModel batchTableModel;
// Adjustment form
private JLabel selectedMedicineLabel;
private JLabel selectedBatchLabel;
private JLabel currentQuantityLabel;
private JSpinner actualQuantitySpinner;
private JTextArea reasonTextArea;
private JButton adjustButton;
private JButton cancelButton;
// Adjustment history
private JTable historyTable;
private DefaultTableModel historyTableModel;
private SimpleDateFormat dateFormatter;
public StockAdjustmentPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy HH:mm");
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Medicine search
medicineSearchField = new JTextField();
medicineSearchField.setFont(new Font("Arial", Font.PLAIN, 14));
medicineSearchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
// Medicine table
String[] medicineColumns = {"Tên Thuốc", "Tổng Tồn", "Trạng Thái"};
medicineTableModel = new DefaultTableModel(medicineColumns, 0) {
@Override
public boolean isCellEditable(int row, int column) { return false; }
};
medicineTable = new JTable(medicineTableModel);
setupMedicineTable();
// Batch table
String[] batchColumns = {"Lô", "HSD", "Tồn Hiện Tại"};
batchTableModel = new DefaultTableModel(batchColumns, 0) {
@Override
public boolean isCellEditable(int row, int column) { return false; }
};
batchTable = new JTable(batchTableModel);
setupBatchTable();
// Adjustment form components
selectedMedicineLabel = new JLabel("Chưa chọn thuốc");
selectedMedicineLabel.setFont(new Font("Arial", Font.BOLD, 16));
selectedBatchLabel = new JLabel("Chưa chọn lô");
selectedBatchLabel.setFont(new Font("Arial", Font.PLAIN, 14));
selectedBatchLabel.setForeground(new Color(100, 100, 100));
currentQuantityLabel = new JLabel("0");
currentQuantityLabel.setFont(new Font("Arial", Font.BOLD, 18));
currentQuantityLabel.setForeground(new Color(63, 81, 181));
actualQuantitySpinner = new JSpinner(new SpinnerNumberModel(0, 0, 99999, 1));
actualQuantitySpinner.setFont(new Font("Arial", Font.PLAIN, 16));
((JSpinner.DefaultEditor) actualQuantitySpinner.getEditor()).getTextField().setHorizontalAlignment(JTextField.CENTER);
reasonTextArea = new JTextArea(3, 30);
reasonTextArea.setFont(new Font("Arial", Font.PLAIN, 14));
reasonTextArea.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 8, 8, 8)
));
reasonTextArea.setLineWrap(true);
reasonTextArea.setWrapStyleWord(true);
adjustButton = new JButton("⚙️ Thực Hiện Kiểm Kê");
adjustButton.setFont(new Font("Arial", Font.BOLD, 16));
adjustButton.setBackground(new Color(255, 152, 0));
adjustButton.setForeground(Color.WHITE);
adjustButton.setFocusPainted(false);
adjustButton.setBorder(BorderFactory.createEmptyBorder(12, 20, 12, 20));
adjustButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
adjustButton.setEnabled(false);
cancelButton = new JButton("Hủy");
cancelButton.setFont(new Font("Arial", Font.PLAIN, 14));
cancelButton.setBackground(new Color(158, 158, 158));
cancelButton.setForeground(Color.WHITE);
cancelButton.setFocusPainted(false);
cancelButton.setBorder(BorderFactory.createEmptyBorder(12, 20, 12, 20));
cancelButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// History table
String[] historyColumns = {"Ngày", "Thuốc", "Lô", "Trước", "Sau", "Chênh Lệch", "Lý Do"};
historyTableModel = new DefaultTableModel(historyColumns, 0) {
@Override
public boolean isCellEditable(int row, int column) { return false; }
};
historyTable = new JTable(historyTableModel);
setupHistoryTable();
}
private void setupMedicineTable() {
medicineTable.setFont(new Font("Arial", Font.PLAIN, 14));
medicineTable.setRowHeight(35);
medicineTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
medicineTable.setBackground(Color.WHITE);
medicineTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
medicineTable.getTableHeader().setBackground(new Color(37, 47, 63));
medicineTable.getTableHeader().setForeground(Color.WHITE);
}
private void setupBatchTable() {
batchTable.setFont(new Font("Arial", Font.PLAIN, 14));
batchTable.setRowHeight(35);
batchTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
batchTable.setBackground(Color.WHITE);
batchTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
batchTable.getTableHeader().setBackground(new Color(37, 47, 63));
batchTable.getTableHeader().setForeground(Color.WHITE);
}
private void setupHistoryTable() {
historyTable.setFont(new Font("Arial", Font.PLAIN, 12));
historyTable.setRowHeight(30);
historyTable.setBackground(Color.WHITE);
historyTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 12));
historyTable.getTableHeader().setBackground(new Color(37, 47, 63));
historyTable.getTableHeader().setForeground(Color.WHITE);
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("KIỂM KÊ KHO THUỐC", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Main content split pane
JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
mainSplit.setDividerLocation(400);
mainSplit.setResizeWeight(0.6);
// Top: Selection and adjustment
JPanel topPanel = createSelectionAndAdjustmentPanel();
mainSplit.setTopComponent(topPanel);
// Bottom: History
RoundedPanel historyPanel = createHistoryPanel();
mainSplit.setBottomComponent(historyPanel);
mainPanel.add(mainSplit, BorderLayout.CENTER);
add(mainPanel, BorderLayout.CENTER);
}
private JPanel createSelectionAndAdjustmentPanel() {
JPanel panel = new JPanel(new BorderLayout(15, 0));
panel.setOpaque(false);
// Left: Medicine and batch selection
JSplitPane selectionSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
selectionSplit.setDividerLocation(300);
selectionSplit.setResizeWeight(0.5);
RoundedPanel medicinePanel = createMedicineSelectionPanel();
RoundedPanel batchPanel = createBatchSelectionPanel();
selectionSplit.setLeftComponent(medicinePanel);
selectionSplit.setRightComponent(batchPanel);
// Right: Adjustment form
RoundedPanel adjustmentPanel = createAdjustmentFormPanel();
panel.add(selectionSplit, BorderLayout.CENTER);
panel.add(adjustmentPanel, BorderLayout.EAST);
return panel;
}
private RoundedPanel createMedicineSelectionPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Header
JLabel headerLabel = new JLabel("1. Chọn Thuốc");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, BorderLayout.NORTH);
// Search
JPanel searchPanel = new JPanel(new BorderLayout(5, 0));
searchPanel.setOpaque(false);
searchPanel.add(new JLabel("🔍"), BorderLayout.WEST);
searchPanel.add(medicineSearchField, BorderLayout.CENTER);
panel.add(searchPanel, BorderLayout.CENTER);
// Table
JScrollPane scrollPane = new JScrollPane(medicineTable);
scrollPane.setPreferredSize(new Dimension(0, 200));
panel.add(scrollPane, BorderLayout.SOUTH);
return panel;
}
private RoundedPanel createBatchSelectionPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Header
JLabel headerLabel = new JLabel("2. Chọn Lô Cần Kiểm Kê");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, BorderLayout.NORTH);
// Table
JScrollPane scrollPane = new JScrollPane(batchTable);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private RoundedPanel createAdjustmentFormPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
panel.setPreferredSize(new Dimension(350, 0));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 5, 8, 5);
gbc.anchor = GridBagConstraints.WEST;
// Header
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
JLabel headerLabel = new JLabel("3. Thực Hiện Kiểm Kê");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, gbc);
// Selected medicine
gbc.gridy = 1; gbc.gridwidth = 1;
panel.add(new JLabel("Thuốc:"), gbc);
gbc.gridx = 1;
panel.add(selectedMedicineLabel, gbc);
// Selected batch
gbc.gridx = 0; gbc.gridy = 2;
panel.add(new JLabel("Lô:"), gbc);
gbc.gridx = 1;
panel.add(selectedBatchLabel, gbc);
// Current quantity
gbc.gridx = 0; gbc.gridy = 3;
panel.add(new JLabel("Tồn hiện tại:"), gbc);
gbc.gridx = 1;
panel.add(currentQuantityLabel, gbc);
// Actual quantity
gbc.gridx = 0; gbc.gridy = 4;
panel.add(new JLabel("Số lượng thực tế:"), gbc);
gbc.gridx = 1;
panel.add(actualQuantitySpinner, gbc);
// Reason
gbc.gridx = 0; gbc.gridy = 5; gbc.anchor = GridBagConstraints.NORTHWEST;
panel.add(new JLabel("Lý do:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.BOTH;
JScrollPane reasonScroll = new JScrollPane(reasonTextArea);
reasonScroll.setPreferredSize(new Dimension(200, 80));
panel.add(reasonScroll, gbc);
// Buttons
gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.CENTER;
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.setOpaque(false);
buttonPanel.add(cancelButton);
buttonPanel.add(adjustButton);
panel.add(buttonPanel, gbc);
return panel;
}
private RoundedPanel createHistoryPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Header
JLabel headerLabel = new JLabel("Lịch Sử Kiểm Kê");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, BorderLayout.NORTH);
// Table
JScrollPane scrollPane = new JScrollPane(historyTable);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private void setupEventListeners() {
// Medicine table selection
medicineTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
onMedicineSelected();
}
});
// Batch table selection
batchTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
onBatchSelected();
}
});
// Adjust button
adjustButton.addActionListener(e -> performStockAdjustment());
// Cancel button
cancelButton.addActionListener(e -> clearSelection());
}
private void onMedicineSelected() {
int row = medicineTable.getSelectedRow();
if (row >= 0) {
String medicineName = (String) medicineTableModel.getValueAt(row, 0);
selectedMedicineLabel.setText(medicineName);
loadBatchesForMedicine(medicineName);
clearBatchSelection();
}
}
private void onBatchSelected() {
int row = batchTable.getSelectedRow();
if (row >= 0) {
String batch = (String) batchTableModel.getValueAt(row, 0);
String currentQty = (String) batchTableModel.getValueAt(row, 2);
selectedBatchLabel.setText("Lô: " + batch);
currentQuantityLabel.setText(currentQty);
try {
int qty = Integer.parseInt(currentQty);
actualQuantitySpinner.setValue(qty);
} catch (NumberFormatException e) {
actualQuantitySpinner.setValue(0);
}
adjustButton.setEnabled(true);
}
}
private void loadBatchesForMedicine(String medicineName) {
batchTableModel.setRowCount(0);
// TODO: Load actual batch data from database
// Mock data
Object[][] mockBatches = {
{"PA202408", "15/08/2025", "45"},
{"PA202411", "30/11/2025", "18"},
{"PA202501", "15/02/2026", "30"}
};
for (Object[] batch : mockBatches) {
batchTableModel.addRow(batch);
}
}
private void performStockAdjustment() {
String medicineName = selectedMedicineLabel.getText();
String batch = selectedBatchLabel.getText();
String currentQtyStr = currentQuantityLabel.getText();
int actualQty = (Integer) actualQuantitySpinner.getValue();
String reason = reasonTextArea.getText().trim();
if (reason.isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập lý do kiểm kê!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
return;
}
try {
int currentQty = Integer.parseInt(currentQtyStr);
int difference = actualQty - currentQty;
// Add to history
historyTableModel.insertRow(0, new Object[]{
dateFormatter.format(new Date()),
medicineName,
batch,
currentQty,
actualQty,
(difference >= 0 ? "+" : "") + difference,
reason
});
// TODO: Update database
JOptionPane.showMessageDialog(this,
"Kiểm kê thành công!\nChênh lệch: " + difference + " đơn vị",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
// Update the batch table
int selectedRow = batchTable.getSelectedRow();
if (selectedRow >= 0) {
batchTableModel.setValueAt(String.valueOf(actualQty), selectedRow, 2);
}
clearSelection();
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "Lỗi dữ liệu số lượng!",
"Lỗi", JOptionPane.ERROR_MESSAGE);
}
}
private void clearSelection() {
medicineTable.clearSelection();
batchTable.clearSelection();
clearBatchSelection();
}
private void clearBatchSelection() {
selectedMedicineLabel.setText("Chưa chọn thuốc");
selectedBatchLabel.setText("Chưa chọn lô");
currentQuantityLabel.setText("0");
actualQuantitySpinner.setValue(0);
reasonTextArea.setText("");
adjustButton.setEnabled(false);
}
public void refreshData() {
// TODO: Load actual data from database
loadMockMedicineData();
loadMockHistoryData();
}
private void loadMockMedicineData() {
medicineTableModel.setRowCount(0);
Object[][] mockData = {
{"Paracetamol 500mg", "63", "Bình thường"},
{"Amoxicillin 250mg", "112", "Bình thường"},
{"Vitamin C 1000mg", "8", "Sắp hết"},
{"Ibuprofen 400mg", "45", "Bình thường"}
};
for (Object[] row : mockData) {
medicineTableModel.addRow(row);
}
}
private void loadMockHistoryData() {
historyTableModel.setRowCount(0);
Object[][] mockHistory = {
{"20/06/2024 14:30", "Paracetamol 500mg", "PA202408", "50", "45", "-5", "Hỏng do ẩm"},
{"18/06/2024 09:15", "Vitamin C 1000mg", "VC202405", "15", "12", "-3", "Hết hạn"},
{"15/06/2024 16:45", "Amoxicillin 250mg", "AM202409", "100", "112", "+12", "Tìm thấy thêm trong kho"}
};
for (Object[] row : mockHistory) {
historyTableModel.addRow(row);
}
}
}
@@ -1,537 +0,0 @@
package BsK.client.ui.component.InventoryPage;
import BsK.client.ui.component.common.RoundedPanel;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
@Slf4j
public class SupplierMasterPanel extends JPanel {
private InventoryPage parentPage;
// Supplier list
private JTextField searchField;
private JTable supplierTable;
private DefaultTableModel supplierTableModel;
// Supplier form
private JTextField companyNameField;
private JTextField contactPersonField;
private JTextField phoneField;
private JTextField emailField;
private JTextField addressField;
private JTextField taxCodeField;
private JTextArea notesArea;
private JComboBox<String> statusCombo;
// Actions
private JButton addButton;
private JButton editButton;
private JButton saveButton;
private JButton cancelButton;
private JButton deleteButton;
private JButton viewHistoryButton;
private boolean isEditing = false;
private int editingRow = -1;
private SimpleDateFormat dateFormatter;
public SupplierMasterPanel(InventoryPage parentPage) {
this.parentPage = parentPage;
this.dateFormatter = new SimpleDateFormat("dd/MM/yyyy");
setLayout(new BorderLayout());
setBackground(new Color(245, 245, 245));
initializeComponents();
layoutComponents();
setupEventListeners();
}
private void initializeComponents() {
// Search field
searchField = new JTextField();
searchField.setFont(new Font("Arial", Font.PLAIN, 14));
searchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
// Supplier table
String[] columns = {"Tên Công Ty", "Người Liên Hệ", "Điện Thoại", "Email", "Trạng Thái"};
supplierTableModel = new DefaultTableModel(columns, 0) {
@Override
public boolean isCellEditable(int row, int column) { return false; }
};
supplierTable = new JTable(supplierTableModel);
setupSupplierTable();
// Form fields
companyNameField = createFormField();
contactPersonField = createFormField();
phoneField = createFormField();
emailField = createFormField();
addressField = createFormField();
taxCodeField = createFormField();
notesArea = new JTextArea(3, 30);
notesArea.setFont(new Font("Arial", Font.PLAIN, 14));
notesArea.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(8, 8, 8, 8)
));
notesArea.setLineWrap(true);
notesArea.setWrapStyleWord(true);
notesArea.setEnabled(false);
statusCombo = new JComboBox<>(new String[]{"Hoạt động", "Tạm dừng", "Ngừng hợp tác"});
statusCombo.setFont(new Font("Arial", Font.PLAIN, 14));
statusCombo.setEnabled(false);
// Action buttons
addButton = createActionButton("➕ Thêm NCC", new Color(76, 175, 80));
editButton = createActionButton("✏️ Sửa", new Color(255, 152, 0));
saveButton = createActionButton("💾 Lưu", new Color(33, 150, 243));
cancelButton = createActionButton("❌ Hủy", new Color(158, 158, 158));
deleteButton = createActionButton("🗑️ Xóa", new Color(244, 67, 54));
viewHistoryButton = createActionButton("📋 Lịch Sử", new Color(156, 39, 176));
saveButton.setEnabled(false);
cancelButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
viewHistoryButton.setEnabled(false);
}
private JTextField createFormField() {
JTextField field = new JTextField();
field.setFont(new Font("Arial", Font.PLAIN, 14));
field.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
field.setEnabled(false);
return field;
}
private JButton createActionButton(String text, Color color) {
JButton button = new JButton(text);
button.setFont(new Font("Arial", Font.BOLD, 14));
button.setBackground(color);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
private void setupSupplierTable() {
supplierTable.setFont(new Font("Arial", Font.PLAIN, 14));
supplierTable.setRowHeight(35);
supplierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
supplierTable.setBackground(Color.WHITE);
supplierTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 14));
supplierTable.getTableHeader().setBackground(new Color(37, 47, 63));
supplierTable.getTableHeader().setForeground(Color.WHITE);
// Status column with color coding
supplierTable.getColumnModel().getColumn(4).setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!isSelected) {
String status = (String) value;
if ("Hoạt động".equals(status)) {
c.setBackground(new Color(232, 245, 233));
setForeground(new Color(76, 175, 80));
} else if ("Tạm dừng".equals(status)) {
c.setBackground(new Color(255, 248, 225));
setForeground(new Color(255, 152, 0));
} else {
c.setBackground(new Color(255, 235, 238));
setForeground(new Color(244, 67, 54));
}
}
setHorizontalAlignment(SwingConstants.CENTER);
return c;
}
});
}
private void layoutComponents() {
JPanel mainPanel = new JPanel(new BorderLayout(0, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
mainPanel.setOpaque(false);
// Title
JLabel titleLabel = new JLabel("QUẢN LÝ NHÀ CUNG CẤP", SwingConstants.CENTER);
titleLabel.setFont(new Font("Arial", Font.BOLD, 28));
titleLabel.setForeground(new Color(37, 47, 63));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Split pane: list + form
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setDividerLocation(650);
splitPane.setResizeWeight(0.65);
// Left: Supplier list
RoundedPanel listPanel = createSupplierListPanel();
splitPane.setLeftComponent(listPanel);
// Right: Supplier form
RoundedPanel formPanel = createSupplierFormPanel();
splitPane.setRightComponent(formPanel);
mainPanel.add(splitPane, BorderLayout.CENTER);
add(mainPanel, BorderLayout.CENTER);
}
private RoundedPanel createSupplierListPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Header with search
JPanel headerPanel = new JPanel(new BorderLayout(10, 0));
headerPanel.setOpaque(false);
JLabel headerLabel = new JLabel("DANH SÁCH NHÀ CUNG CẤP");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
JPanel searchPanel = new JPanel(new BorderLayout(5, 0));
searchPanel.setOpaque(false);
searchPanel.add(new JLabel("🔍"), BorderLayout.WEST);
searchPanel.add(searchField, BorderLayout.CENTER);
searchPanel.setPreferredSize(new Dimension(250, 35));
headerPanel.add(headerLabel, BorderLayout.WEST);
headerPanel.add(searchPanel, BorderLayout.EAST);
panel.add(headerPanel, BorderLayout.NORTH);
// Table
JScrollPane scrollPane = new JScrollPane(supplierTable);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(200, 200, 200)));
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private RoundedPanel createSupplierFormPanel() {
RoundedPanel panel = new RoundedPanel(10, Color.WHITE, true);
panel.setLayout(new BorderLayout(0, 15));
panel.setBorder(BorderFactory.createEmptyBorder(25, 25, 25, 25));
// Header
JLabel headerLabel = new JLabel("THÔNG TIN NHÀ CUNG CẤP");
headerLabel.setFont(new Font("Arial", Font.BOLD, 16));
headerLabel.setForeground(new Color(37, 47, 63));
panel.add(headerLabel, BorderLayout.NORTH);
// Form fields
JPanel formPanel = new JPanel(new GridBagLayout());
formPanel.setOpaque(false);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 5, 8, 10);
gbc.anchor = GridBagConstraints.WEST;
// Company info
addFormRow(formPanel, gbc, 0, "Tên công ty:", companyNameField);
addFormRow(formPanel, gbc, 1, "Người liên hệ:", contactPersonField);
addFormRow(formPanel, gbc, 2, "Điện thoại:", phoneField);
addFormRow(formPanel, gbc, 3, "Email:", emailField);
addFormRow(formPanel, gbc, 4, "Mã số thuế:", taxCodeField);
addFormRow(formPanel, gbc, 5, "Trạng thái:", statusCombo);
// Address
gbc.gridx = 0; gbc.gridy = 6; gbc.anchor = GridBagConstraints.NORTHWEST;
formPanel.add(new JLabel("Địa chỉ:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
addressField.setPreferredSize(new Dimension(250, 60));
formPanel.add(addressField, gbc);
// Notes
gbc.gridx = 0; gbc.gridy = 7; gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.NONE;
formPanel.add(new JLabel("Ghi chú:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.BOTH;
JScrollPane notesScroll = new JScrollPane(notesArea);
notesScroll.setPreferredSize(new Dimension(250, 80));
formPanel.add(notesScroll, gbc);
panel.add(formPanel, BorderLayout.CENTER);
// Action buttons
JPanel buttonPanel = new JPanel(new GridLayout(3, 2, 8, 8));
buttonPanel.setOpaque(false);
buttonPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
buttonPanel.add(addButton);
buttonPanel.add(editButton);
buttonPanel.add(saveButton);
buttonPanel.add(cancelButton);
buttonPanel.add(deleteButton);
buttonPanel.add(viewHistoryButton);
panel.add(buttonPanel, BorderLayout.SOUTH);
return panel;
}
private void addFormRow(JPanel parent, GridBagConstraints gbc, int row, String label, JComponent component) {
gbc.gridx = 0; gbc.gridy = row; gbc.fill = GridBagConstraints.NONE;
parent.add(new JLabel(label), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
component.setPreferredSize(new Dimension(250, 30));
parent.add(component, gbc);
}
private void setupEventListeners() {
// Table selection
supplierTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
onSupplierSelected();
}
});
// Action buttons
addButton.addActionListener(e -> startAdd());
editButton.addActionListener(e -> startEdit());
saveButton.addActionListener(e -> saveSupplier());
cancelButton.addActionListener(e -> cancelOperation());
deleteButton.addActionListener(e -> deleteSupplier());
viewHistoryButton.addActionListener(e -> viewPurchaseHistory());
}
private void onSupplierSelected() {
int row = supplierTable.getSelectedRow();
boolean hasSelection = row >= 0;
editButton.setEnabled(hasSelection && !isEditing);
deleteButton.setEnabled(hasSelection && !isEditing);
viewHistoryButton.setEnabled(hasSelection && !isEditing);
if (hasSelection) {
loadSupplierToForm(row);
}
}
private void loadSupplierToForm(int row) {
// TODO: Load actual supplier data
companyNameField.setText((String) supplierTableModel.getValueAt(row, 0));
contactPersonField.setText((String) supplierTableModel.getValueAt(row, 1));
phoneField.setText((String) supplierTableModel.getValueAt(row, 2));
emailField.setText((String) supplierTableModel.getValueAt(row, 3));
statusCombo.setSelectedItem(supplierTableModel.getValueAt(row, 4));
// Mock additional data
taxCodeField.setText("0123456789");
addressField.setText("123 Đường ABC, Phường XYZ, Quận 1, TP.HCM");
notesArea.setText("Nhà cung cấp uy tín, giao hàng đúng hẹn.");
}
private void startAdd() {
isEditing = true;
editingRow = -1;
enableFormFields(true);
clearForm();
addButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
viewHistoryButton.setEnabled(false);
saveButton.setEnabled(true);
cancelButton.setEnabled(true);
}
private void startEdit() {
int row = supplierTable.getSelectedRow();
if (row < 0) return;
isEditing = true;
editingRow = row;
enableFormFields(true);
addButton.setEnabled(false);
editButton.setEnabled(false);
deleteButton.setEnabled(false);
viewHistoryButton.setEnabled(false);
saveButton.setEnabled(true);
cancelButton.setEnabled(true);
}
private void saveSupplier() {
if (!validateForm()) return;
Object[] rowData = {
companyNameField.getText(),
contactPersonField.getText(),
phoneField.getText(),
emailField.getText(),
statusCombo.getSelectedItem()
};
if (editingRow >= 0) {
// Update existing
for (int i = 0; i < rowData.length; i++) {
supplierTableModel.setValueAt(rowData[i], editingRow, i);
}
JOptionPane.showMessageDialog(this, "Cập nhật nhà cung cấp thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
} else {
// Add new
supplierTableModel.addRow(rowData);
JOptionPane.showMessageDialog(this, "Thêm nhà cung cấp mới thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
}
// TODO: Save to database
cancelOperation();
}
private void cancelOperation() {
isEditing = false;
editingRow = -1;
enableFormFields(false);
addButton.setEnabled(true);
saveButton.setEnabled(false);
cancelButton.setEnabled(false);
onSupplierSelected(); // Refresh button states
}
private void deleteSupplier() {
int row = supplierTable.getSelectedRow();
if (row < 0) return;
String companyName = (String) supplierTableModel.getValueAt(row, 0);
int confirm = JOptionPane.showConfirmDialog(this,
"Bạn có chắc chắn muốn xóa nhà cung cấp '" + companyName + "'?",
"Xác nhận xóa", JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
supplierTableModel.removeRow(row);
clearForm();
JOptionPane.showMessageDialog(this, "Xóa nhà cung cấp thành công!",
"Thành công", JOptionPane.INFORMATION_MESSAGE);
// TODO: Delete from database
}
}
private void viewPurchaseHistory() {
int row = supplierTable.getSelectedRow();
if (row < 0) return;
String companyName = (String) supplierTableModel.getValueAt(row, 0);
// Create history dialog
JDialog historyDialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(this),
"Lịch Sử Mua Hàng - " + companyName, true);
historyDialog.setSize(800, 500);
historyDialog.setLocationRelativeTo(this);
// History table
String[] historyColumns = {"Ngày", "Số HĐ", "Tổng Tiền", "Trạng Thái", "Ghi Chú"};
DefaultTableModel historyModel = new DefaultTableModel(historyColumns, 0);
JTable historyTable = new JTable(historyModel);
historyTable.setRowHeight(30);
// Mock history data
Object[][] historyData = {
{"20/06/2024", "HD001", "15.650.000", "Đã thanh toán", "Giao đúng hẹn"},
{"15/06/2024", "HD002", "8.900.000", "Đã thanh toán", ""},
{"10/06/2024", "HD003", "22.300.000", "Chưa thanh toán", "Chờ duyệt"},
{"05/06/2024", "HD004", "12.800.000", "Đã thanh toán", "Có chiết khấu"}
};
for (Object[] row : historyData) {
historyModel.addRow(row);
}
JScrollPane historyScroll = new JScrollPane(historyTable);
historyDialog.add(historyScroll, BorderLayout.CENTER);
// Summary panel
JPanel summaryPanel = new JPanel(new FlowLayout());
summaryPanel.add(new JLabel("Tổng số đơn hàng: 4 | Tổng giá trị: 59.650.000 VNĐ"));
historyDialog.add(summaryPanel, BorderLayout.SOUTH);
historyDialog.setVisible(true);
}
private void enableFormFields(boolean enabled) {
companyNameField.setEnabled(enabled);
contactPersonField.setEnabled(enabled);
phoneField.setEnabled(enabled);
emailField.setEnabled(enabled);
addressField.setEnabled(enabled);
taxCodeField.setEnabled(enabled);
notesArea.setEnabled(enabled);
statusCombo.setEnabled(enabled);
}
private void clearForm() {
companyNameField.setText("");
contactPersonField.setText("");
phoneField.setText("");
emailField.setText("");
addressField.setText("");
taxCodeField.setText("");
notesArea.setText("");
statusCombo.setSelectedIndex(0);
}
private boolean validateForm() {
if (companyNameField.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập tên công ty!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
companyNameField.requestFocus();
return false;
}
if (contactPersonField.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập người liên hệ!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
contactPersonField.requestFocus();
return false;
}
if (phoneField.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Vui lòng nhập số điện thoại!",
"Thông báo", JOptionPane.WARNING_MESSAGE);
phoneField.requestFocus();
return false;
}
return true;
}
public void refreshData() {
// TODO: Load actual data from database
loadMockSupplierData();
}
private void loadMockSupplierData() {
supplierTableModel.setRowCount(0);
Object[][] mockData = {
{"Công ty TNHH Dược phẩm ABC", "Nguyễn Văn A", "0901234567", "contact@abc.com", "Hoạt động"},
{"Công ty CP Dược Hậu Giang", "Trần Thị B", "0902345678", "info@haugiangpharma.com", "Hoạt động"},
{"Công ty TNHH Dược phẩm Traphaco", "Lê Văn C", "0903456789", "sales@traphaco.com", "Hoạt động"},
{"Công ty CP Dược phẩm OPC", "Phạm Thị D", "0904567890", "business@opc.vn", "Tạm dừng"},
{"Công ty TNHH Imexpharm", "Hoàng Văn E", "0905678901", "order@imexpharm.com", "Hoạt động"}
};
for (Object[] row : mockData) {
supplierTableModel.addRow(row);
}
log.info("Supplier master data loaded - " + mockData.length + " suppliers");
}
}
@@ -1,296 +0,0 @@
package BsK.client.ui.component.InventoryPage.common;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
* Centralized button factory for consistent UI/UX across the Inventory Management module.
* Defines standardized button styles for different action types.
*/
public class InventoryButtonFactory {
// Brand color palette (softer, professional tones)
public static final Color PRIMARY_COLOR = new Color(63, 81, 181); // Professional blue
public static final Color PRIMARY_HOVER = new Color(48, 63, 159); // Darker blue on hover
public static final Color SECONDARY_COLOR = new Color(158, 158, 158); // Neutral grey
public static final Color SECONDARY_HOVER = new Color(117, 117, 117); // Darker grey on hover
public static final Color DESTRUCTIVE_COLOR = new Color(229, 57, 53); // Softer red
public static final Color DESTRUCTIVE_HOVER = new Color(198, 40, 40); // Darker red on hover
public static final Color SPECIAL_COLOR = new Color(97, 97, 97); // Dark grey
public static final Color SPECIAL_HOVER = new Color(66, 66, 66); // Darker grey on hover
public static final Color SUCCESS_COLOR = new Color(67, 160, 71); // Softer green
public static final Color SUCCESS_HOVER = new Color(56, 142, 60); // Darker green on hover
// Standard button dimensions
public static final Dimension STANDARD_BUTTON_SIZE = new Dimension(140, 40);
public static final Dimension LARGE_BUTTON_SIZE = new Dimension(180, 45);
public static final Dimension SMALL_BUTTON_SIZE = new Dimension(100, 35);
// Font configurations
public static final Font BUTTON_FONT = new Font("Arial", Font.BOLD, 14);
public static final Font LARGE_BUTTON_FONT = new Font("Arial", Font.BOLD, 16);
public static final Font SMALL_BUTTON_FONT = new Font("Arial", Font.BOLD, 12);
/**
* Creates a primary action button (Save, Create, Confirm)
* Solid background with white text
*/
public static JButton createPrimaryButton(String text) {
return createStandardButton(text, PRIMARY_COLOR, PRIMARY_HOVER, Color.WHITE, BUTTON_FONT, STANDARD_BUTTON_SIZE);
}
public static JButton createPrimaryButton(String text, Dimension size) {
Font font = size.equals(LARGE_BUTTON_SIZE) ? LARGE_BUTTON_FONT :
size.equals(SMALL_BUTTON_SIZE) ? SMALL_BUTTON_FONT : BUTTON_FONT;
return createStandardButton(text, PRIMARY_COLOR, PRIMARY_HOVER, Color.WHITE, font, size);
}
/**
* Creates a secondary action button (Cancel, Close)
* Grey background or outline with colored text
*/
public static JButton createSecondaryButton(String text) {
return createStandardButton(text, SECONDARY_COLOR, SECONDARY_HOVER, Color.WHITE, BUTTON_FONT, STANDARD_BUTTON_SIZE);
}
public static JButton createSecondaryButton(String text, Dimension size) {
Font font = size.equals(LARGE_BUTTON_SIZE) ? LARGE_BUTTON_FONT :
size.equals(SMALL_BUTTON_SIZE) ? SMALL_BUTTON_FONT : BUTTON_FONT;
return createStandardButton(text, SECONDARY_COLOR, SECONDARY_HOVER, Color.WHITE, font, size);
}
/**
* Creates a destructive action button (Delete, Remove)
* Solid red background with white text
*/
public static JButton createDestructiveButton(String text) {
return createStandardButton(text, DESTRUCTIVE_COLOR, DESTRUCTIVE_HOVER, Color.WHITE, BUTTON_FONT, STANDARD_BUTTON_SIZE);
}
public static JButton createDestructiveButton(String text, Dimension size) {
Font font = size.equals(LARGE_BUTTON_SIZE) ? LARGE_BUTTON_FONT :
size.equals(SMALL_BUTTON_SIZE) ? SMALL_BUTTON_FONT : BUTTON_FONT;
return createStandardButton(text, DESTRUCTIVE_COLOR, DESTRUCTIVE_HOVER, Color.WHITE, font, size);
}
/**
* Creates a special action button (Print, Export)
* Dark grey background with white text
*/
public static JButton createSpecialButton(String text) {
return createStandardButton(text, SPECIAL_COLOR, SPECIAL_HOVER, Color.WHITE, BUTTON_FONT, STANDARD_BUTTON_SIZE);
}
public static JButton createSpecialButton(String text, Dimension size) {
Font font = size.equals(LARGE_BUTTON_SIZE) ? LARGE_BUTTON_FONT :
size.equals(SMALL_BUTTON_SIZE) ? SMALL_BUTTON_FONT : BUTTON_FONT;
return createStandardButton(text, SPECIAL_COLOR, SPECIAL_HOVER, Color.WHITE, font, size);
}
/**
* Creates a success action button (Add, Create New)
* Solid green background with white text
*/
public static JButton createSuccessButton(String text) {
return createStandardButton(text, SUCCESS_COLOR, SUCCESS_HOVER, Color.WHITE, BUTTON_FONT, STANDARD_BUTTON_SIZE);
}
public static JButton createSuccessButton(String text, Dimension size) {
Font font = size.equals(LARGE_BUTTON_SIZE) ? LARGE_BUTTON_FONT :
size.equals(SMALL_BUTTON_SIZE) ? SMALL_BUTTON_FONT : BUTTON_FONT;
return createStandardButton(text, SUCCESS_COLOR, SUCCESS_HOVER, Color.WHITE, font, size);
}
/**
* Creates an outline button (secondary style with border)
* Transparent background with colored border and text
*/
public static JButton createOutlineButton(String text, Color borderColor) {
JButton button = new JButton(text);
button.setFont(BUTTON_FONT);
button.setForeground(borderColor);
button.setBackground(Color.WHITE);
button.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(borderColor, 2),
BorderFactory.createEmptyBorder(8, 15, 8, 15)
));
button.setFocusPainted(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.setPreferredSize(STANDARD_BUTTON_SIZE);
button.setMinimumSize(STANDARD_BUTTON_SIZE);
button.setMaximumSize(STANDARD_BUTTON_SIZE);
// Hover effect for outline buttons
Color hoverBackground = new Color(borderColor.getRed(), borderColor.getGreen(), borderColor.getBlue(), 20);
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
if (button.isEnabled()) {
button.setBackground(hoverBackground);
}
}
@Override
public void mouseExited(MouseEvent e) {
button.setBackground(Color.WHITE);
}
});
return button;
}
/**
* Creates an icon-only button for use in tables and compact areas
*/
public static JButton createIconButton(String icon, Color backgroundColor, Color hoverColor, String tooltip) {
JButton button = new JButton(icon);
button.setFont(new Font("Arial", Font.PLAIN, 16));
button.setBackground(backgroundColor);
button.setForeground(Color.WHITE);
button.setBorder(BorderFactory.createEmptyBorder(5, 8, 5, 8));
button.setFocusPainted(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.setPreferredSize(new Dimension(35, 30));
button.setMinimumSize(new Dimension(35, 30));
button.setMaximumSize(new Dimension(35, 30));
if (tooltip != null && !tooltip.isEmpty()) {
button.setToolTipText(tooltip);
}
// Hover effect
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
if (button.isEnabled()) {
button.setBackground(hoverColor);
}
}
@Override
public void mouseExited(MouseEvent e) {
if (button.isEnabled()) {
button.setBackground(backgroundColor);
}
}
});
return button;
}
/**
* Core method for creating standardized buttons
*/
private static JButton createStandardButton(String text, Color backgroundColor, Color hoverColor,
Color textColor, Font font, Dimension size) {
JButton button = new JButton(text);
button.setFont(font);
button.setBackground(backgroundColor);
button.setForeground(textColor);
button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
button.setFocusPainted(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.setPreferredSize(size);
button.setMinimumSize(size);
button.setMaximumSize(size);
// Hover effect
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
if (button.isEnabled()) {
button.setBackground(hoverColor);
}
}
@Override
public void mouseExited(MouseEvent e) {
if (button.isEnabled()) {
button.setBackground(backgroundColor);
} else {
button.setBackground(new Color(200, 200, 200)); // Disabled color
}
}
});
// Disabled state styling
button.addPropertyChangeListener("enabled", evt -> {
if ((Boolean) evt.getNewValue()) {
button.setBackground(backgroundColor);
button.setForeground(textColor);
} else {
button.setBackground(new Color(200, 200, 200));
button.setForeground(new Color(150, 150, 150));
}
});
return button;
}
/**
* Creates a button group panel with standardized spacing
*/
public static JPanel createButtonGroup(JButton... buttons) {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
panel.setOpaque(false);
for (JButton button : buttons) {
panel.add(button);
}
return panel;
}
/**
* Creates a button group panel with custom alignment
*/
public static JPanel createButtonGroup(int alignment, JButton... buttons) {
JPanel panel = new JPanel(new FlowLayout(alignment, 10, 0));
panel.setOpaque(false);
for (JButton button : buttons) {
panel.add(button);
}
return panel;
}
/**
* Creates a split button group (left and right aligned)
*/
public static JPanel createSplitButtonGroup(JButton[] leftButtons, JButton[] rightButtons) {
JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(false);
if (leftButtons != null && leftButtons.length > 0) {
JPanel leftPanel = createButtonGroup(FlowLayout.LEFT, leftButtons);
panel.add(leftPanel, BorderLayout.WEST);
}
if (rightButtons != null && rightButtons.length > 0) {
JPanel rightPanel = createButtonGroup(FlowLayout.RIGHT, rightButtons);
panel.add(rightPanel, BorderLayout.EAST);
}
return panel;
}
// Standard icon constants for common actions
public static final String ICON_DELETE = "🗑️";
public static final String ICON_EDIT = "✏️";
public static final String ICON_VIEW = "👁️";
public static final String ICON_PRINT = "🖨️";
public static final String ICON_EXPORT = "📤";
public static final String ICON_SEARCH = "🔍";
public static final String ICON_ADD = "➕";
public static final String ICON_SAVE = "💾";
public static final String ICON_CANCEL = "❌";
public static final String ICON_REFRESH = "🔄";
public static final String ICON_SETTINGS = "⚙️";
public static final String ICON_REPORT = "📊";
public static final String ICON_WARNING = "⚠️";
public static final String ICON_SUCCESS = "✅";
public static final String ICON_INFO = "ℹ️";
}
@@ -1,187 +0,0 @@
package BsK.client.ui.component.InventoryPage.common;
import java.awt.*;
/**
* Centralized color scheme for the Inventory Management module.
* Uses softer, professional tones for better readability and visual appeal.
*/
public class InventoryColorScheme {
// Primary brand colors (softer, professional tones)
public static final Color PRIMARY_BLUE = new Color(63, 81, 181);
public static final Color PRIMARY_BLUE_LIGHT = new Color(92, 107, 192);
public static final Color PRIMARY_BLUE_DARK = new Color(48, 63, 159);
// Status colors (less saturated for easier viewing)
public static final Color SUCCESS_GREEN = new Color(67, 160, 71);
public static final Color SUCCESS_GREEN_LIGHT = new Color(232, 245, 233);
public static final Color WARNING_ORANGE = new Color(255, 167, 38);
public static final Color WARNING_ORANGE_LIGHT = new Color(255, 248, 225);
public static final Color DANGER_RED = new Color(229, 57, 53);
public static final Color DANGER_RED_LIGHT = new Color(255, 235, 238);
public static final Color INFO_BLUE = new Color(33, 150, 243);
public static final Color INFO_BLUE_LIGHT = new Color(227, 242, 253);
// Neutral colors
public static final Color GREY_50 = new Color(250, 250, 250);
public static final Color GREY_100 = new Color(245, 245, 245);
public static final Color GREY_200 = new Color(238, 238, 238);
public static final Color GREY_300 = new Color(224, 224, 224);
public static final Color GREY_400 = new Color(189, 189, 189);
public static final Color GREY_500 = new Color(158, 158, 158);
public static final Color GREY_600 = new Color(117, 117, 117);
public static final Color GREY_700 = new Color(97, 97, 97);
public static final Color GREY_800 = new Color(66, 66, 66);
public static final Color GREY_900 = new Color(33, 33, 33);
// Background colors
public static final Color BACKGROUND_PRIMARY = Color.WHITE;
public static final Color BACKGROUND_SECONDARY = GREY_50;
public static final Color BACKGROUND_TERTIARY = GREY_100;
// Text colors
public static final Color TEXT_PRIMARY = new Color(37, 47, 63);
public static final Color TEXT_SECONDARY = GREY_600;
public static final Color TEXT_DISABLED = GREY_400;
public static final Color TEXT_ON_DARK = Color.WHITE;
// Border colors
public static final Color BORDER_LIGHT = GREY_200;
public static final Color BORDER_MEDIUM = GREY_300;
public static final Color BORDER_DARK = GREY_400;
// Dashboard card colors (softer backgrounds)
public static final Color CARD_LOW_STOCK_BG = new Color(255, 242, 242);
public static final Color CARD_LOW_STOCK_BORDER = new Color(229, 115, 115);
public static final Color CARD_EXPIRING_BG = new Color(255, 248, 225);
public static final Color CARD_EXPIRING_BORDER = new Color(255, 193, 7);
public static final Color CARD_VALUE_BG = new Color(232, 245, 233);
public static final Color CARD_VALUE_BORDER = new Color(102, 187, 106);
public static final Color CARD_ITEMS_BG = new Color(232, 234, 246);
public static final Color CARD_ITEMS_BORDER = new Color(92, 107, 192);
// Table colors
public static final Color TABLE_HEADER_BG = new Color(37, 47, 63);
public static final Color TABLE_HEADER_TEXT = Color.WHITE;
public static final Color TABLE_ROW_EVEN = Color.WHITE;
public static final Color TABLE_ROW_ODD = new Color(248, 249, 250);
public static final Color TABLE_ROW_SELECTED = new Color(232, 234, 246);
public static final Color TABLE_ROW_HOVER = new Color(245, 245, 245);
public static final Color TABLE_BORDER = GREY_200;
// Status indicator colors in tables
public static final Color STATUS_ACTIVE_BG = SUCCESS_GREEN_LIGHT;
public static final Color STATUS_ACTIVE_TEXT = SUCCESS_GREEN;
public static final Color STATUS_WARNING_BG = WARNING_ORANGE_LIGHT;
public static final Color STATUS_WARNING_TEXT = WARNING_ORANGE;
public static final Color STATUS_DANGER_BG = DANGER_RED_LIGHT;
public static final Color STATUS_DANGER_TEXT = DANGER_RED;
public static final Color STATUS_INFO_BG = INFO_BLUE_LIGHT;
public static final Color STATUS_INFO_TEXT = INFO_BLUE;
// Form colors
public static final Color FORM_FIELD_BG = Color.WHITE;
public static final Color FORM_FIELD_BORDER = BORDER_LIGHT;
public static final Color FORM_FIELD_BORDER_FOCUS = PRIMARY_BLUE;
public static final Color FORM_FIELD_DISABLED_BG = GREY_100;
public static final Color FORM_FIELD_DISABLED_BORDER = GREY_300;
// Navigation colors
public static final Color NAV_BACKGROUND = new Color(37, 47, 63);
public static final Color NAV_TEXT = Color.WHITE;
public static final Color NAV_TEXT_INACTIVE = new Color(150, 150, 150);
public static final Color NAV_HOVER = new Color(52, 62, 78);
public static final Color NAV_ACTIVE = PRIMARY_BLUE;
// Utility methods for creating variations
/**
* Creates a lighter version of the given color
*/
public static Color lighten(Color color, float factor) {
int red = Math.min(255, (int) (color.getRed() + (255 - color.getRed()) * factor));
int green = Math.min(255, (int) (color.getGreen() + (255 - color.getGreen()) * factor));
int blue = Math.min(255, (int) (color.getBlue() + (255 - color.getBlue()) * factor));
return new Color(red, green, blue, color.getAlpha());
}
/**
* Creates a darker version of the given color
*/
public static Color darken(Color color, float factor) {
int red = Math.max(0, (int) (color.getRed() * (1 - factor)));
int green = Math.max(0, (int) (color.getGreen() * (1 - factor)));
int blue = Math.max(0, (int) (color.getBlue() * (1 - factor)));
return new Color(red, green, blue, color.getAlpha());
}
/**
* Creates a color with specified opacity
*/
public static Color withOpacity(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
/**
* Gets status color based on string value
*/
public static Color getStatusColor(String status) {
if (status == null) return TEXT_SECONDARY;
switch (status.toLowerCase()) {
case "hoạt động":
case "active":
case "bình thường":
case "normal":
return STATUS_ACTIVE_TEXT;
case "sắp hết":
case "low":
case "warning":
case "cảnh báo":
return STATUS_WARNING_TEXT;
case "hết hàng":
case "out":
case "danger":
case "nguy hiểm":
return STATUS_DANGER_TEXT;
case "tạm dừng":
case "paused":
case "info":
return STATUS_INFO_TEXT;
default:
return TEXT_SECONDARY;
}
}
/**
* Gets status background color based on string value
*/
public static Color getStatusBackgroundColor(String status) {
if (status == null) return BACKGROUND_PRIMARY;
switch (status.toLowerCase()) {
case "hoạt động":
case "active":
case "bình thường":
case "normal":
return STATUS_ACTIVE_BG;
case "sắp hết":
case "low":
case "warning":
case "cảnh báo":
return STATUS_WARNING_BG;
case "hết hàng":
case "out":
case "danger":
case "nguy hiểm":
return STATUS_DANGER_BG;
case "tạm dừng":
case "paused":
case "info":
return STATUS_INFO_BG;
default:
return BACKGROUND_PRIMARY;
}
}
}
@@ -2,6 +2,7 @@ package BsK.client.ui.component.common;
import BsK.client.LocalStorage;
import BsK.client.ui.component.MainFrame;
import BsK.client.ui.component.InfoPage.InfoDialog;
import BsK.common.packet.req.LogoutRequest;
import BsK.common.util.network.NetworkUtil;
import BsK.client.network.handler.ClientHandler;
@@ -71,16 +72,14 @@ public class NavBar extends JPanel {
welcomePanel.add(welcomeLabel);
userPanel.add(welcomePanel, new GridBagConstraints());
String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Kho", "Thanh toán", "Cài đặt", "Thông tin"};
String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "InventoryPage", "CheckoutPage", "SettingsPage", "InfoPage"};
String[] iconFiles = {"dashboard.png", "health-check.png", "database.png", "warehouse.png", "cashier-machine.png", "settings.png", "info.png"};
String[] navBarItems = {"Thống kê", "Thăm khám", "Dữ liệu", "Cài đặt", "Thông tin"};
String[] destination = {"DashboardPage", "CheckUpPage", "PatientDataPage", "SettingsPage", "InfoPage"};
String[] iconFiles = {"dashboard.png", "health-check.png", "database.png","settings.png", "info.png"};
Color[] navItemColors = {
new Color(51, 135, 204), // Darker Blue
new Color(66, 157, 21), // Darker Green
new Color(200, 138, 16), // Darker Orange
new Color(204, 62, 63), // Darker Red
new Color(91, 37, 167), // Darker Purple
new Color(15, 155, 155), // Darker Cyan
new Color(196, 27, 36) // Darker Red-Orange
};
@@ -133,6 +132,12 @@ public class NavBar extends JPanel {
itemPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if ("InfoPage".equals(dest)) {
InfoDialog infoDialog = new InfoDialog();
infoDialog.setVisible(true);
return;
}
if (activeNavItem != null && activeNavItem != label) {
JPanel prevPanel = (JPanel) activeNavItem.getParent();
prevPanel.setBackground(navItemColors[findNavItemIndex(activeNavItem.getText(), navBarItems)].brighter());