Fix the style problem of this code. Clear the excess spacing at the top of the main content and move all the main content at top. Also, make the overview boxes smller and just right size and fix the styling of the dtae and time as well as the user icon and usernmae: Here is the code: <?php
/**
* Plugin Name: Dashboard Overview Snippet with Chart
* Description: Displays a dashboard overview on a page with real-time updates and a stock chart.
*/
// --- Database Connection (Using WordPress $wpdb) ---
global $wpdb;
// --- *** NEW: Dedicated AJAX Handler Function *** ---
function dashboard_overview_ajax_handler() {
// Optional: Add nonce check for security
// check_ajax_referer('dashboard_data_nonce', 'security');
$data_type = isset($_REQUEST['data_type']) ? sanitize_key($_REQUEST['data_type']) : null;
$response = ['error' => 'Invalid data type specified']; // Default error
if ($data_type) {
// Note: The fetch_* functions need access to $wpdb, ensure they are defined globally or passed if needed.
// Since they are defined outside this function but use `global $wpdb;` inside, it should work here.
switch ($data_type) {
case 'overview_boxes':
$response = [
'userCount' => fetch_wp_user_count(),
'categoryCount' => fetch_wp_category_count(),
'productCount' => fetch_wp_product_count(),
'totalSales' => fetch_wp_total_sales(),
];
break;
case 'highest_selling_products':
$response = fetch_wp_highest_selling_products();
break;
case 'latest_sales':
$response = fetch_wp_latest_sales();
break;
case 'recent_products':
$response = fetch_wp_recent_products();
break;
default:
error_log("Dashboard AJAX Error: Unknown data_type requested - " . $data_type);
// Keep default error message
break;
}
wp_send_json_success($response);
} else {
error_log("Dashboard AJAX Error: data_type parameter missing.");
wp_send_json_error($response, 400); // Send error if data_type is missing
}
// wp_die(); // Included in wp_send_json_* functions
}
// --- *** NEW: Hook the handler to WordPress AJAX actions *** ---
// IMPORTANT: These lines ideally belong in functions.php or a plugin file.
// Placing them here might work, but it's not the standard/recommended practice.
add_action('wp_ajax_get_dashboard_data', 'dashboard_overview_ajax_handler'); // For logged-in users
// add_action('wp_ajax_nopriv_get_dashboard_data', 'dashboard_overview_ajax_handler'); // Uncomment if needed for non-logged-in users
// --- Data Fetching Functions (Using NO prefix as requested) ---
function fetch_wp_user_count() {
global $wpdb;
$users_table_name = 'users';
// WP Users table *always* uses prefix
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $users_table_name)) != $users_table_name) { return 0; }
$count = $wpdb->get_var("SELECT COUNT(*) FROM $users_table_name");
return $count ? $count : 0;
}
function fetch_wp_category_count() {
global $wpdb;
$category_table_name = 'categories'; // NO prefix
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $category_table_name)) != $category_table_name) { return 0; }
$count = $wpdb->get_var("SELECT COUNT(*) FROM $category_table_name");
return $count ? $count : 0;
}
function fetch_wp_product_count() {
global $wpdb;
$product_table_name = 'products'; // NO prefix
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $product_table_name)) != $product_table_name) { return 0; }
$count = $wpdb->get_var("SELECT COUNT(*) FROM $product_table_name");
return $count ? $count : 0;
}
function fetch_wp_total_sales() {
global $wpdb;
$sales_table_name = 'orders'; // NO prefix
$sales_amount_column = 'total_amount';
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $sales_table_name)) != $sales_table_name) { return '0.00'; }
$total = $wpdb->get_var($wpdb->prepare("SELECT SUM($sales_amount_column) FROM $sales_table_name WHERE status = %s", 'Paid'));
return $total ? number_format_i18n($total, 2) : '0.00';
}
function fetch_wp_highest_selling_products() {
global $wpdb;
$products = [];
$products_table_name = 'products';
$order_items_table_name = 'order_items';
$orders_table_name = 'orders';
// Debug: Check table existence
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $products_table_name)) != $products_table_name ||
$wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $order_items_table_name)) != $order_items_table_name ||
$wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $orders_table_name)) != $orders_table_name) {
error_log("Dashboard Error: Required tables not found");
return $products;
}
$results = $wpdb->get_results($wpdb->prepare("
SELECT
p.name AS product_name,
SUM(oi.quantity) AS items_sold,
SUM(oi.amount) AS total_sales
FROM {$order_items_table_name} oi
JOIN {$products_table_name} p ON oi.product_id = p.id
JOIN {$orders_table_name} o ON oi.order_id = o.id
WHERE o.status = 'Paid'
GROUP BY p.id, p.name
ORDER BY items_sold DESC
LIMIT %d", 5), ARRAY_A);
if ($results) {
foreach ($results as $row) {
$products[] = [
'name' => $row['product_name'],
'items_sold' => $row['items_sold'],
'total_sales' => number_format_i18n($row['total_sales'], 2),
];
}
} else {
error_log("Dashboard Debug: No highest selling products found");
}
return $products;
}
function fetch_wp_latest_sales() {
global $wpdb;
$sales = [];
$order_items_table_name = 'order_items';
$orders_table_name = 'orders';
$products_table_name = 'products';
// Debug: Check table existence
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $orders_table_name)) != $orders_table_name ||
$wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $order_items_table_name)) != $order_items_table_name ||
$wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $products_table_name)) != $products_table_name) {
error_log("Dashboard Error: Required tables not found");
return $sales;
}
$results = $wpdb->get_results($wpdb->prepare("
SELECT
o.id AS order_id,
o.total_amount,
o.created_at,
GROUP_CONCAT(DISTINCT p.name SEPARATOR ', ') AS product_names
FROM {$orders_table_name} o
LEFT JOIN {$order_items_table_name} oi ON o.id = oi.order_id
LEFT JOIN {$products_table_name} p ON oi.product_id = p.id
WHERE o.status = 'Paid'
GROUP BY o.id, o.total_amount, o.created_at
ORDER BY o.created_at DESC
LIMIT %d", 5), ARRAY_A);
if ($results) {
foreach ($results as $row) {
$sales[] = [
'item_no' => 'Order #' . $row['order_id'],
'product_name' => $row['product_names'] ?? 'N/A',
'total_sales' => number_format_i18n($row['total_amount'], 2),
];
}
} else {
error_log("Dashboard Debug: No latest sales found");
}
return $sales;
}
// --- UPDATED fetch_wp_recent_products function ---
function fetch_wp_recent_products() {
global $wpdb;
$products = [];
$products_table_name = 'products'; // NO prefix
// *** IMPORTANT: Define the *actual* date column name in your 'products' table here ***
$products_date_column = 'created_at'; // <--- CHANGE THIS ('created_at', 'date_created', 'date_added', etc.)
if($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $products_table_name)) != $products_table_name) {
error_log("Dashboard Snippet Error: Table '{$products_table_name}' not found.");
return $products; // Table doesn't exist
}
// Check if the specified date column exists
$columns = $wpdb->get_col("DESC $products_table_name");
if (!in_array($products_date_column, $columns)) {
error_log("Dashboard Snippet Error: Specified date column '{$products_date_column}' not found in table '{$products_table_name}'. Please check the function.");
return $products; // Column doesn't exist, cannot sort
}
// Prepare and execute the query
$query = $wpdb->prepare("
SELECT name, {$products_date_column} AS created_at_alias
FROM {$products_table_name}
WHERE name IS NOT NULL AND name != '' AND {$products_date_column} IS NOT NULL
ORDER BY {$products_date_column} DESC
LIMIT %d",
5);
$results = $wpdb->get_results($query, ARRAY_A);
// Check for query errors
if ($results === null) {
error_log("Dashboard Snippet Error: WPDB query failed for recent products. Error: " . $wpdb->last_error . " Query: " . $query);
return $products; // Query failed
}
// Process results
if ($results) {
foreach ($results as $row) {
// Ensure name and date exist before adding
if (isset($row['name']) && isset($row['created_at_alias'])) {
$products[] = [
'name' => $row['name'],
'date_added' => mysql2date(get_option('date_format'), $row['created_at_alias']), // Use the alias
];
}
}
}
return $products;
}
// --- END UPDATED fetch_wp_recent_products function ---
// --- Fetch Data for Stock Chart ---
$product_table_name_chart = 'products'; // NO prefix
$products_for_chart = [];
$chart_data_error = '';
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $product_table_name_chart)) == $product_table_name_chart) {
$products_for_chart_raw = $wpdb->get_results( "SELECT name, in_stock FROM {$product_table_name_chart} WHERE name IS NOT NULL AND name != '' ORDER BY name ASC", ARRAY_A );
if ($products_for_chart_raw === null) { $chart_data_error = "Error querying products table: " . $wpdb->last_error; error_log("Dashboard Chart Error: " . $chart_data_error);
} elseif (empty($products_for_chart_raw)) { $chart_data_error = "No products with names found in the '{$product_table_name_chart}' table.";
} else { foreach ($products_for_chart_raw as $product) { $products_for_chart[] = [ 'name' => $product['name'], 'in_stock' => intval($product['in_stock'] ?? 0) ]; } }
} else { $chart_data_error = "Error: Products table '{$product_table_name_chart}' not found."; }
$chart_labels = json_encode(wp_list_pluck($products_for_chart, 'name'));
$chart_data = json_encode(wp_list_pluck($products_for_chart, 'in_stock'));
// --- END: Fetch Data for Stock Chart ---
// --- Initial Data for Page Load ---
$initialUserCount = fetch_wp_user_count();
$initialCategoryCount = fetch_wp_category_count();
$initialProductCount = fetch_wp_product_count();
$initialTotalSales = fetch_wp_total_sales();
$initialHighestSellingProducts = fetch_wp_highest_selling_products();
$initialLatestSales = fetch_wp_latest_sales();
$initialRecentProducts = fetch_wp_recent_products();
$currency_symbol = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '₱';
?>
<!DOCTYPE html>
<html lang="<?php language_attributes(); ?>">
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Overview - <?php bloginfo('name'); ?></title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<?php // wp_head(); ?>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0; padding: 0;
background-color: #f4f7f9; color: #333;
}
.dashboard-container { display: flex; }
.sidebar {
width: 240px;
background-color: #D1B48C;
color: #000;
height: 100vh;
position: fixed;
left: 0; top: 0;
overflow-y: auto;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.sidebar-header {
padding: 20px;
text-align: center;
}
.sidebar-header .inventory-name {
font-size: 17px;
font-weight: bold;
color: #000;
}
.sidebar-menu {
padding: 0;
}
.sidebar-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.sidebar-menu li a {
display: flex;
align-items: center;
padding: 12px 20px;
text-decoration: none;
color: #000;
transition: 0.3s;
font-size: 16px;
}
.sidebar-menu li a i {
margin-right: 12px;
width: 20px;
text-align: center;
}
.sidebar-menu li a:hover {
background-color: #ffffff;
color: #000;
}
.main-content {
margin-left: 240px;
padding: 20px;
flex-grow: 1;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
padding: 15px 30px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.header-left .date-time {
font-size: 16px;
color: #6c757d;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
font-size: 17px;
color: #333;
}
/* === DASHBOARD LAYOUT === */
#dashboard-overview-snippet-container .top-section { display: flex; flex-wrap: wrap; gap: 25px; margin-bottom: 35px; }
#dashboard-overview-snippet-container .overview-boxes { flex: 1 1 400px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
#dashboard-overview-snippet-container .overview-boxes .box { background-color: #fff; padding: 20px 15px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.06); border: 1px solid #e9ecef; display: flex; align-items: center; gap: 15px; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; width: 100%; }
#dashboard-overview-snippet-container .overview-boxes .box:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.09); }
#dashboard-overview-snippet-container .overview-boxes .box i.icon { font-size: 24px; color: #fff; border-radius: 50%; padding: 12px; width: 55px; height: 55px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
#dashboard-overview-snippet-container .overview-boxes .box:nth-child(1) i.icon { background-color: #17a2b8; }
#dashboard-overview-snippet-container .overview-boxes .box:nth-child(2) i.icon { background-color: #28a745; }
#dashboard-overview-snippet-container .overview-boxes .box:nth-child(3) i.icon { background-color: #ffc107; }
#dashboard-overview-snippet-container .overview-boxes .box:nth-child(4) i.icon { background-color: #dc3545; }
#dashboard-overview-snippet-container .overview-boxes .box .detail { text-align: left; overflow: hidden; }
#dashboard-overview-snippet-container .overview-boxes .box .count { font-size: 24px; font-weight: 600; color: #343a40; line-height: 1.1; margin-bottom: 4px; white-space: nowrap; }
#dashboard-overview-snippet-container .overview-boxes .box .label { font-size: 14px; color: #6c757d; white-space: nowrap; }
/* --- Chart Container Styles --- */
#dashboard-overview-snippet-container .chart-container { flex: 1.5 1 500px; background-color: #fff; padding: 25px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.07); border: 1px solid #e9ecef; position: relative; height: auto; min-height: 400px; }
#dashboard-overview-snippet-container .chart-container h2 { margin-top: 0; margin-bottom: 25px; color: #343a40; text-align: center; font-size: 1.5em; border-bottom: 1px solid #dee2e6; padding-bottom: 15px; font-weight: 600; }
#dashboard-overview-snippet-container #stockChartCanvas { max-width: 100%; height: 300px !important; display: block; }
#dashboard-overview-snippet-container .chart-container .chart-message { text-align: center; padding: 20px; color: #6c757d; font-style: italic; }
#dashboard-overview-snippet-container .chart-container .chart-error-message { color: #dc3545; font-weight: bold; }
/* --- Dashboard Tables Styles --- */
#dashboard-overview-snippet-container .dashboard-tables { display: grid; grid-template-columns: 1fr; gap: 35px; }
#dashboard-overview-snippet-container .dashboard-tables .table-container { background-color: #fff; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.07); border: 1px solid #e9ecef; padding: 30px; overflow-x: auto; }
#dashboard-overview-snippet-container .dashboard-tables .table-container h2 { margin-top: 0; margin-bottom: 25px; color: #343a40; border-bottom: 1px solid #dee2e6; padding-bottom: 15px; font-size: 1.2em; font-weight: 600; }
#dashboard-overview-snippet-container .dashboard-tables table { width: 100%; border-collapse: collapse; border-spacing: 0; }
#dashboard-overview-snippet-container .dashboard-tables th,
#dashboard-overview-snippet-container .dashboard-tables td { padding: 16px 20px; text-align: left; border-bottom: 1px solid #dee2e6; font-size: 1.05em; vertical-align: middle; line-height: 1.5; }
#dashboard-overview-snippet-container .dashboard-tables th { background-color: #f8f9fa; font-weight: 600; color: #495057; white-space: nowrap; font-size: 1em; text-transform: uppercase; letter-spacing: 0.5px; }
#dashboard-overview-snippet-container .dashboard-tables tbody tr:last-child td { border-bottom: none; }
#dashboard-overview-snippet-container .dashboard-tables tbody tr:hover { background-color: #f1f3f5; }
#dashboard-overview-snippet-container #highest-selling-table td:nth-child(2),
#dashboard-overview-snippet-container #highest-selling-table td:nth-child(3),
#dashboard-overview-snippet-container #latest-sales-table td:nth-child(3) { text-align: right; }
#dashboard-overview-snippet-container #recent-products-table td:nth-child(2) { text-align: right; white-space: nowrap; }
/* --- Message Styles --- */
#dashboard-overview-snippet-container .notice-wrap { margin-bottom: 15px; }
#dashboard-overview-snippet-container .notice { padding: 10px 15px; border-radius: 4px; border-left: 4px solid; }
#dashboard-overview-snippet-container .notice-error { background-color: #f8d7da; border-color: #dc3545; color: #721c24; }
#dashboard-overview-snippet-container .ajax-error-notice { margin-top: 15px; }
/* --- Responsive Adjustments --- */
@media (max-width: 992px) { #dashboard-overview-snippet-container .top-section { flex-direction: column; } #dashboard-overview-snippet-container .overview-boxes, #dashboard-overview-snippet-container .chart-container { flex-basis: auto; width: 100%; } #dashboard-overview-snippet-container .chart-container { min-height: 350px; } }
@media (max-width: 576px) { #dashboard-overview-snippet-container .overview-boxes { grid-template-columns: 1fr; } #dashboard-overview-snippet-container .overview-boxes .box { gap: 10px; } #dashboard-overview-snippet-container .overview-boxes .box i.icon { width: 45px; height: 45px; font-size: 20px; padding: 10px; } #dashboard-overview-snippet-container .overview-boxes .box .count { font-size: 20px; } #dashboard-overview-snippet-container .overview-boxes .box .label { font-size: 13px; } #dashboard-overview-snippet-container .chart-container { padding: 15px; } #dashboard-overview-snippet-container .chart-container h2 { font-size: 1.3em; margin-bottom: 15px; padding-bottom: 10px;} #dashboard-overview-snippet-container .dashboard-tables .table-container { padding: 15px; } #dashboard-overview-snippet-container .dashboard-tables th, #dashboard-overview-snippet-container .dashboard-tables td { padding: 10px 8px; font-size: 0.95em;} }
</style>
</head>
<body <?php body_class(); ?>>
<div class="dashboard-container">
<aside class="sidebar">
<div class="sidebar-header">
<div class="inventory-name">ArKi Inventory</div>
</div>
<div class="sidebar-menu">
<ul>
<li><a href="http://localhost/inventory/index.php/admin-page/"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<?php
session_start();
$current_username = isset($_SESSION['username']) ? $_SESSION['username'] : '';
if ($current_username === 'admin') :
?>
<li><a href="http://localhost/inventory/index.php/usersmanagement/"><i class="fas fa-users-cog"></i> User Management</a></li>
<li><a href="http://localhost/inventory/index.php/category-page/"><i class="fas fa-list"></i> Categories</a></li>
<li><a href="http://localhost/inventory/index.php/products/"><i class="fas fa-boxes"></i> Products</a></li>
<li><a href="http://localhost/inventory/index.php/order/"><i class="fas fa-shopping-cart"></i> Orders</a></li>
<li><a href="http://localhost/inventory/index.php/view-order/"><i class="fas fa-eye"></i> View Orders</a></li>
<li><a href="http://localhost/inventory/index.php/sales/"><i class="fas fa-chart-line"></i> Sales & Report</a></li>
<li><a href="http://localhost/inventory/index.php/report/"><i class="fas fa-file-alt"></i> Inventory Report</a></li>
<li><a href="http://localhost/inventory/index.php/history/"><i class="fas fa-history"></i> Inventory History</a></li>
<?php else : ?>
<li><a href="http://localhost/inventory/index.php/category-page/"><i class="fas fa-list"></i> Categories</a></li>
<li><a href="http://localhost/inventory/index.php/products/"><i class="fas fa-boxes"></i> Products</a></li>
<li><a href="http://localhost/inventory/index.php/order/"><i class="fas fa-shopping-cart"></i> Orders</a></li>
<?php endif; ?>
<li><a href="http://localhost/inventory/index.php/sign-in2/" onclick="return confirm('Are you sure you want to log out?');"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
</ul>
</div>
</aside>
<main class="main-content">
<header class="header">
<div class="header-left">
<span class="date-time" id="current-date-time"></span>
</div>
<div class="header-right">
<i class="fas fa-user-circle"></i>
<span id="current-username-header">
<?php echo isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : 'Guest'; ?>
</span>
</div>
</header>
<div id="dashboard-overview-snippet-container">
<header class="header">
<h1>Dashboard Overview</h1>
</header>
<!-- NEW WRAPPER for top two columns -->
<section class="top-section">
<!-- Left Column: Overview Boxes (Now a Grid) -->
<section class="overview-boxes">
<div class="box"> <i class="fas fa-users icon"></i> <div class="detail"> <div class="count" id="user-count"><?php echo esc_html($initialUserCount); ?></div> <div class="label">Users</div> </div> </div>
<div class="box"> <i class="fas fa-tags icon"></i> <div class="detail"> <div class="count" id="category-count"><?php echo esc_html($initialCategoryCount); ?></div> <div class="label">Categories</div> </div> </div>
<div class="box"> <i class="fas fa-box-open icon"></i> <div class="detail"> <div class="count" id="product-count"><?php echo esc_html($initialProductCount); ?></div> <div class="label">Products</div> </div> </div>
<div class="box"> <i class="fas fa-chart-line icon"></i> <div class="detail"> <div class="count" id="total-sales"><?php echo esc_html($currency_symbol); ?><?php echo esc_html($initialTotalSales); ?></div> <div class="label">Total Sales</div> </div> </div>
</section>
<!-- Right Column: Chart -->
<section class="chart-container">
<h2>Product Stock Overview</h2>
<canvas id="stockChartCanvas" style="display: none;"></canvas> <!-- Hide initially -->
<div id="chart-message-area">
<?php if (!empty($chart_data_error)): ?>
<p class="chart-message chart-error-message"><?php echo esc_html($chart_data_error); ?></p>
<?php elseif (empty($products_for_chart)): ?>
<p class="chart-message">No product data available to display the chart.</p>
<?php else: ?>
<p class="chart-message" id="chart-loading-message">Loading chart...</p>
<?php endif; ?>
</div>
</section>
</section> <!-- END of top-section -->
<!-- Tables Section (Remains below the top section) -->
<section class="dashboard-tables">
<div class="table-container">
<h2>Highest Selling Products</h2>
<table id="highest-selling-table">
<thead> <tr> <th>Product Name</th> <th>Items Sold</th> <th>Total Sales</th> </tr> </thead>
<tbody>
<?php if (empty($initialHighestSellingProducts)): ?> <tr><td colspan="3" style="text-align: center;">No sales data available</td></tr>
<?php else: foreach ($initialHighestSellingProducts as $product): ?> <tr> <td><?php echo esc_html($product['name']); ?></td> <td style="text-align:right;"><?php echo esc_html($product['items_sold']); ?></td> <td style="text-align:right;"><?php echo esc_html($currency_symbol); ?><?php echo esc_html($product['total_sales']); ?></td> </tr> <?php endforeach; endif; ?>
</tbody>
</table>
</div>
<div class="table-container">
<h2>Latest Sales</h2>
<table id="latest-sales-table">
<thead> <tr> <th>Item No</th> <th>Product(s)</th> <th>Total Sales</th> </tr> </thead>
<tbody>
<?php if (empty($initialLatestSales)): ?> <tr><td colspan="3" style="text-align: center;">No recent sales</td></tr>
<?php else: foreach ($initialLatestSales as $sale): ?> <tr> <td><?php echo esc_html($sale['item_no']); ?></td> <td><?php echo esc_html($sale['product_name']); ?></td> <td style="text-align:right;"><?php echo esc_html($currency_symbol); ?><?php echo esc_html($sale['total_sales']); ?></td> </tr> <?php endforeach; endif; ?>
</tbody>
</table>
</div>
<div class="table-container">
<h2>Recently Added Products</h2>
<table id="recent-products-table">
<thead> <tr> <th>Product Name</th> <th>Date Added</th> </tr> </thead>
<tbody>
<?php if (empty($initialRecentProducts)): ?> <tr><td colspan="2" style="text-align: center;">No recently added products</td></tr>
<?php else: foreach ($initialRecentProducts as $product): ?> <tr> <td><?php echo esc_html($product['name']); ?></td> <td style="text-align:right; white-space:nowrap;"><?php echo esc_html($product['date_added']); ?></td> </tr> <?php endforeach; endif; ?>
</tbody>
</table>
</div>
</section>
</div> <!-- End #dashboard-overview-snippet-container -->
</main>
</div>
<!-- Include Chart.js library -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<!-- UPDATED AJAX Update Script -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const container = document.getElementById('dashboard-overview-snippet-container'); if (!container) return;
const ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>'; // Use admin-ajax.php
const currencySymbol = '<?php echo esc_js($currency_symbol); ?>';
// const dashboardNonce = '<?php // echo wp_create_nonce("dashboard_data_nonce"); ?>'; // If using nonces
function fetchData(dataType, callback) {
const url = new URL(ajaxUrl);
// *** ADD THE ACTION PARAMETER ***
url.searchParams.append('action', 'get_dashboard_data'); // Matches add_action hook
url.searchParams.append('data_type', dataType);
// url.searchParams.append('security', dashboardNonce); // Add if using nonces
console.log(`Fetching data for: ${dataType} from URL: ${url.toString()}`); // Debug log
fetch(url)
.then(response => {
if (!response.ok) {
console.error(`HTTP error! status: ${response.status} for ${dataType}`, response);
return response.text().then(text => { console.error("Response Text:", text); throw new Error(`HTTP error! status: ${response.status}`); });
}
return response.json();
})
.then(data => {
if (typeof data.success === 'undefined') { console.error("AJAX response missing 'success' property:", data); throw new Error(`Invalid AJAX response format for ${dataType}`); }
if (data.success === false) { console.error(`AJAX Error (${dataType}):`, data.data?.message || data.data || 'Unknown server error'); throw new Error(data.data?.message || data.data || `AJAX error fetching ${dataType}`); }
callback(data.data); // Pass the actual data payload
})
.catch(error => {
console.error(`Error fetching ${dataType}:`, error);
const errorContainer = container.querySelector('.main-content') || container;
if(errorContainer && !container.querySelector('.ajax-error-notice')) {
const notice = document.createElement('div'); notice.className = 'notice notice-error ajax-error-notice'; notice.innerHTML = `<p>Error loading dashboard data (${dataType}). Please check console or try again later.</p>`; errorContainer.insertBefore(notice, errorContainer.firstChild);
}
});
}
// Keep the rest of the AJAX update functions
function updateOverviewBoxes() { fetchData('overview_boxes', data => { const userCountEl = container.querySelector('#user-count'); const categoryCountEl = container.querySelector('#category-count'); const productCountEl = container.querySelector('#product-count'); const totalSalesEl = container.querySelector('#total-sales'); if(userCountEl && typeof data.userCount !== 'undefined') userCountEl.textContent = data.userCount; if(categoryCountEl && typeof data.categoryCount !== 'undefined') categoryCountEl.textContent = data.categoryCount; if(productCountEl && typeof data.productCount !== 'undefined') productCountEl.textContent = data.productCount; if(totalSalesEl && typeof data.totalSales !== 'undefined') totalSalesEl.textContent = currencySymbol + data.totalSales; }); }
function updateTable(tableId, dataType, renderFunction) { fetchData(dataType, data => { const tableBody = container.querySelector(`#${tableId} tbody`); if (!tableBody) return; tableBody.innerHTML = ''; if (!data || !Array.isArray(data) || data.length === 0) { const colSpan = tableBody.closest('table')?.querySelector('thead tr')?.childElementCount || 1; tableBody.innerHTML = `<tr><td colspan="${colSpan}" style="text-align: center;">No data available</td></tr>`; } else { data.forEach(item => { tableBody.innerHTML += renderFunction(item); }); } }); }
function renderHighestSellingRow(product) { return `<tr><td>${escapeHtml(product.name)}</td><td style="text-align:right;">${escapeHtml(product.items_sold)}</td><td style="text-align:right;">${currencySymbol}${escapeHtml(product.total_sales)}</td></tr>`; }
function renderLatestSalesRow(sale) { return `<tr><td>${escapeHtml(sale.item_no)}</td><td>${escapeHtml(sale.product_name)}</td><td style="text-align:right;">${currencySymbol}${escapeHtml(sale.total_sales)}</td></tr>`; }
function renderRecentProductsRow(product) { return `<tr><td>${escapeHtml(product.name)}</td><td style="text-align:right; white-space:nowrap;">${escapeHtml(product.date_added)}</td></tr>`; }
function escapeHtml(unsafe) { if (unsafe === null || typeof unsafe === 'undefined') return ''; return unsafe.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); }
updateOverviewBoxes(); updateTable('highest-selling-table', 'highest_selling_products', renderHighestSellingRow); updateTable('latest-sales-table', 'latest_sales', renderLatestSalesRow); updateTable('recent-products-table', 'recent_products', renderRecentProductsRow);
const updateInterval = 30000; setInterval(updateOverviewBoxes, updateInterval); setInterval(() => updateTable('highest-selling-table', 'highest_selling_products', renderHighestSellingRow), updateInterval); setInterval(() => updateTable('latest-sales-table', 'latest_sales', renderLatestSalesRow), updateInterval); setInterval(() => updateTable('recent-products-table', 'recent_products', renderRecentProductsRow), updateInterval);
});
</script>
<!-- END UPDATED AJAX Update Script -->
<!-- Chart Initialization JavaScript with MORE Debugging -->
<script>
// Paste the existing Chart initialization script here
document.addEventListener('DOMContentLoaded', function() {
console.log("[Chart Script] DOMContentLoaded fired. Initializing chart script...");
const ctx = document.getElementById('stockChartCanvas');
const chartContainer = document.querySelector('#dashboard-overview-snippet-container .chart-container');
const messageArea = document.getElementById('chart-message-area');
const loadingMessage = document.getElementById('chart-loading-message');
console.log("[Chart Script] Canvas Element (ctx):", ctx); console.log("[Chart Script] Chart Container:", chartContainer); console.log("[Chart Script] Message Area:", messageArea); console.log("[Chart Script] Loading Message Element:", loadingMessage);
function showChartMessage(message, isError = false) { console.log(`[Chart Script] Attempting to show message (isError: ${isError}): "${message}"`); if (messageArea) { messageArea.innerHTML = `<p class="chart-message ${isError ? 'chart-error-message' : ''}">${escapeHtml(message)}</p>`; console.log("[Chart Script] Message area updated."); } else { console.error("[Chart Script] Cannot show message - messageArea element not found!"); } if (ctx) { ctx.style.display = 'none'; console.log("[Chart Script] Canvas hidden."); } else { console.warn("[Chart Script] Cannot hide canvas - ctx element not found!"); } }
let phpLabels = null; let phpData = null; let phpError = null;
console.log("[Chart Script] Reading PHP variables...");
try { phpLabels = <?php echo $chart_labels ?? 'null'; ?>; phpData = <?php echo $chart_data ?? 'null'; ?>; phpError = <?php echo json_encode($chart_data_error); ?>; console.log("[Chart Script] PHP Labels Raw:", JSON.stringify(phpLabels)); console.log("[Chart Script] PHP Data Raw:", JSON.stringify(phpData)); console.log("[Chart Script] PHP Error Message:", phpError); } catch (e) { console.error("[Chart Script] FATAL: JavaScript Error reading PHP variables:", e); showChartMessage("Internal error reading chart data. Check console.", true); return; }
console.log("[Chart Script] Starting validation checks...");
if (phpError) { console.error("[Chart Script] CHECK FAILED: PHP Error reported:", phpError); showChartMessage(phpError, true); return; } console.log("[Chart Script] CHECK PASSED: No PHP Error reported.");
if (!ctx) { console.error("[Chart Script] CHECK FAILED: Canvas element #stockChartCanvas not found!"); showChartMessage("Chart canvas element is missing in HTML.", true); return; } console.log("[Chart Script] CHECK PASSED: Canvas element found.");
if (!messageArea) { console.error("[Chart Script] CHECK WARNING: Message area #chart-message-area not found! Error messages might not display correctly."); } else { console.log("[Chart Script] CHECK PASSED: Message area found."); }
if (phpLabels === null || phpData === null) { console.warn("[Chart Script] CHECK FAILED: PHP variables for chart labels or data are null."); showChartMessage("Chart data is unavailable from server (null).", false); return; } console.log("[Chart Script] CHECK PASSED: PHP variables are not null.");
if (!Array.isArray(phpLabels) || !Array.isArray(phpData)) { console.error("[Chart Script] CHECK FAILED: PHP data for chart is not in array format.", phpLabels, phpData); showChartMessage("Invalid chart data format received (not arrays).", true); return; } console.log("[Chart Script] CHECK PASSED: PHP data are arrays.");
if (phpLabels.length === 0) { console.warn("[Chart Script] CHECK FAILED: Chart labels array is empty."); showChartMessage("No product stock data available to display (empty array).", false); return; } console.log("[Chart Script] CHECK PASSED: Labels array is not empty.");
if (phpLabels.length !== phpData.length) { console.error(`[Chart Script] CHECK FAILED: Chart labels (${phpLabels.length}) and data (${phpData.length}) arrays have different lengths!`); showChartMessage("Data mismatch: Labels and stock counts do not align.", true); return; } console.log("[Chart Script] CHECK PASSED: Label and data array lengths match.");
console.log("[Chart Script] All validation checks passed. Preparing to render chart...");
if (loadingMessage) { loadingMessage.remove(); console.log("[Chart Script] Removed loading message element."); } else { console.warn("[Chart Script] Loading message element (#chart-loading-message) not found to remove."); }
if (messageArea) { messageArea.innerHTML = ''; console.log("[Chart Script] Cleared message area content."); }
ctx.style.display = 'block'; console.log("[Chart Script] Canvas display set to 'block'.");
try { console.log("[Chart Script] Initializing Chart.js..."); function generateColors(count) { const colors = []; if (count <= 0) return colors; const hueStep = 360 / count; for (let i = 0; i < count; i++) { const hue = i * hueStep; const saturation = 70 + (Math.random() * 15); const lightness = 55 + (Math.random() * 10); colors.push(`hsla(${hue.toFixed(0)}, ${saturation.toFixed(0)}%, ${lightness.toFixed(0)}%, 0.85)`); } return colors; } const backgroundColors = generateColors(phpLabels.length); const borderColors = backgroundColors.map(color => color.replace('0.85', '1')); new Chart(ctx, { type: 'bar', data: { labels: phpLabels, datasets: [{ label: 'Stock Quantity', data: phpData, backgroundColor: backgroundColors, borderColor: borderColors, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'x', plugins: { legend: { display: false }, title: { display: false }, tooltip: { callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) label += ': '; if (context.parsed.y !== null) label += context.parsed.y; return label; } } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Stock Quantity' }, ticks: { precision: 0 } }, x: { title: { display: false } } } } }); console.log("[Chart Script] Chart successfully initialized."); } catch (e) { console.error("[Chart Script] FATAL: Chart.js Initialization Error:", e); showChartMessage("Failed to create the chart display. Check console.", true); }
});
function escapeHtml(unsafe) { if (unsafe === null || typeof unsafe === 'undefined') return ''; return unsafe.toString() .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }
</script>
<!-- --- END: Chart Initialization JavaScript --- -->
<?php // wp_footer(); ?>
<script>
function updateDateTime() {
const now = new Date();
const options = {
weekday: 'short', year: 'numeric', month: 'short',
day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit'
};
document.getElementById("current-date-time").textContent = now.toLocaleDateString('en-US', options);
}
setInterval(updateDateTime, 1000);
updateDateTime();
</script>
</body>
</html>
<?php
// End of PHP Snippet
?>