836 lines
25 KiB
PHP
836 lines
25 KiB
PHP
<?php // phpcs:ignoreFile
|
|
/**
|
|
* Mill Data Dashboard - Visual analytics and trends
|
|
*/
|
|
|
|
require __DIR__ . '/../session.php';
|
|
require __DIR__ . '/../userAccess.php';
|
|
require __DIR__ . '/../includes/millnames.php';
|
|
|
|
// ============================================================================
|
|
// Database Configuration
|
|
// ============================================================================
|
|
$config = [
|
|
'server' => '192.168.0.16',
|
|
'database' => 'lasucaai',
|
|
'username' => 'lasucaai',
|
|
'password' => 'is413#dfslw',
|
|
];
|
|
|
|
// ============================================================================
|
|
// Database Connection
|
|
// ============================================================================
|
|
function getDashboardConnection($config) {
|
|
$connectionOptions = [
|
|
"Database" => $config['database'],
|
|
"Uid" => $config['username'],
|
|
"PWD" => $config['password'],
|
|
"TrustServerCertificate" => true,
|
|
"Encrypt" => false,
|
|
];
|
|
|
|
$conn = sqlsrv_connect($config['server'], $connectionOptions);
|
|
return $conn ?: null;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Data Queries
|
|
// ============================================================================
|
|
|
|
function getDashboardStats($conn) {
|
|
$sql = "SELECT
|
|
COUNT(DISTINCT r.ReportId) as TotalReports,
|
|
COUNT(DISTINCT r.SourceFileName) as TotalFiles,
|
|
COUNT(DISTINCT r.MillName) as TotalMills,
|
|
COUNT(m.MetricId) as TotalMetrics,
|
|
MIN(r.BeginningDate) as EarliestDate,
|
|
MAX(r.EndingDate) as LatestDate
|
|
FROM dbo.MillDataReports r
|
|
LEFT JOIN dbo.MillDataMetrics m ON r.ReportId = m.ReportId";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql);
|
|
return sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
|
}
|
|
|
|
function getRecentReports($conn, $limit = 10) {
|
|
$sql = "SELECT TOP(?) r.ReportId, r.SourceFileName, r.MillName,
|
|
r.BeginningDate, r.EndingDate, r.CropDays, r.ProcessedAt
|
|
FROM dbo.MillDataReports r
|
|
ORDER BY r.ProcessedAt DESC";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql, [$limit]);
|
|
$reports = [];
|
|
|
|
if ($stmt) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$reports[] = $row;
|
|
}
|
|
}
|
|
|
|
return $reports;
|
|
}
|
|
|
|
function getKeyMetricTrends($conn) {
|
|
// Get key grinding/production metrics across all reports with dates
|
|
$sql = "SELECT r.MillName, r.BeginningDate, r.EndingDate, r.SourceFileName,
|
|
m.MetricName, m.RunValueNumeric, m.ToDateValueNumeric, m.Category
|
|
FROM dbo.MillDataMetrics m
|
|
JOIN dbo.MillDataReports r ON m.ReportId = r.ReportId
|
|
WHERE m.MetricName IN (
|
|
'TC Ground This Week',
|
|
'TC Ground To Date',
|
|
'Hours This Week',
|
|
'Lost Time This Week',
|
|
'TC/GH This Week',
|
|
'Efficiency This Week'
|
|
)
|
|
AND m.RunValueNumeric IS NOT NULL
|
|
ORDER BY r.BeginningDate, r.MillName";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql);
|
|
$trends = [];
|
|
|
|
if ($stmt) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$trends[] = $row;
|
|
}
|
|
}
|
|
|
|
return $trends;
|
|
}
|
|
|
|
function getMillPerformanceSummary($conn) {
|
|
// Get latest performance for each mill
|
|
$sql = "WITH LatestReports AS (
|
|
SELECT MillName, MAX(ProcessedAt) as LatestProcessed
|
|
FROM dbo.MillDataReports
|
|
GROUP BY MillName
|
|
)
|
|
SELECT r.MillName, r.BeginningDate, r.EndingDate, r.SourceFileName,
|
|
MAX(CASE WHEN m.MetricName = 'TC Ground This Week' THEN m.RunValueNumeric END) as TCGround,
|
|
MAX(CASE WHEN m.MetricName = 'Hours This Week' THEN m.RunValueNumeric END) as HoursRun,
|
|
MAX(CASE WHEN m.MetricName = 'Lost Time This Week' THEN m.RunValueNumeric END) as LostTime,
|
|
MAX(CASE WHEN m.MetricName = 'TC/GH This Week' THEN m.RunValueNumeric END) as TCPerHour,
|
|
MAX(CASE WHEN m.MetricName = 'TC Ground To Date' THEN m.ToDateValueNumeric END) as TCToDate
|
|
FROM dbo.MillDataReports r
|
|
JOIN LatestReports lr ON r.MillName = lr.MillName AND r.ProcessedAt = lr.LatestProcessed
|
|
LEFT JOIN dbo.MillDataMetrics m ON r.ReportId = m.ReportId
|
|
GROUP BY r.MillName, r.BeginningDate, r.EndingDate, r.SourceFileName
|
|
ORDER BY r.MillName";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql);
|
|
$summary = [];
|
|
|
|
if ($stmt) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$summary[] = $row;
|
|
}
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
function getCategoryBreakdown($conn) {
|
|
$sql = "SELECT m.Category, COUNT(*) as MetricCount,
|
|
COUNT(DISTINCT r.ReportId) as ReportCount
|
|
FROM dbo.MillDataMetrics m
|
|
JOIN dbo.MillDataReports r ON m.ReportId = r.ReportId
|
|
WHERE m.Category IS NOT NULL
|
|
GROUP BY m.Category
|
|
ORDER BY COUNT(*) DESC";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql);
|
|
$breakdown = [];
|
|
|
|
if ($stmt) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$breakdown[] = $row;
|
|
}
|
|
}
|
|
|
|
return $breakdown;
|
|
}
|
|
|
|
function getWeeklyTrendData($conn) {
|
|
// Get TC Ground per week for charting
|
|
$sql = "SELECT r.MillName, r.BeginningDate,
|
|
SUM(CASE WHEN m.MetricName = 'TC Ground This Week' THEN m.RunValueNumeric END) as TCGround,
|
|
SUM(CASE WHEN m.MetricName = 'Hours This Week' THEN m.RunValueNumeric END) as HoursRun
|
|
FROM dbo.MillDataReports r
|
|
JOIN dbo.MillDataMetrics m ON r.ReportId = m.ReportId
|
|
WHERE m.MetricName IN ('TC Ground This Week', 'Hours This Week')
|
|
GROUP BY r.MillName, r.BeginningDate
|
|
ORDER BY r.BeginningDate, r.MillName";
|
|
|
|
$stmt = sqlsrv_query($conn, $sql);
|
|
$data = [];
|
|
|
|
if ($stmt) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$data[] = $row;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
function formatDate($date) {
|
|
if ($date instanceof DateTime) {
|
|
return $date->format('m/d/Y');
|
|
}
|
|
return $date ?? '-';
|
|
}
|
|
|
|
function formatShortDate($date) {
|
|
if ($date instanceof DateTime) {
|
|
return $date->format('M j');
|
|
}
|
|
return $date ?? '-';
|
|
}
|
|
|
|
// ============================================================================
|
|
// Main Logic
|
|
// ============================================================================
|
|
$conn = getDashboardConnection($config);
|
|
$connectionError = $conn === null;
|
|
|
|
$stats = ['TotalReports' => 0, 'TotalFiles' => 0, 'TotalMills' => 0, 'TotalMetrics' => 0];
|
|
$recentReports = [];
|
|
$keyTrends = [];
|
|
$millPerformance = [];
|
|
$categoryBreakdown = [];
|
|
$weeklyTrends = [];
|
|
$millNameLookup = [];
|
|
|
|
if (!$connectionError) {
|
|
// Load mill name mappings
|
|
$millNameLookup = getMillNames($conn);
|
|
|
|
$stats = getDashboardStats($conn);
|
|
$recentReports = getRecentReports($conn, 10);
|
|
$keyTrends = getKeyMetricTrends($conn);
|
|
$millPerformance = getMillPerformanceSummary($conn);
|
|
$categoryBreakdown = getCategoryBreakdown($conn);
|
|
$weeklyTrends = getWeeklyTrendData($conn);
|
|
|
|
sqlsrv_close($conn);
|
|
}
|
|
|
|
// Prepare chart data
|
|
$chartLabels = [];
|
|
$chartDatasets = [];
|
|
$millColors = [
|
|
'East Mill' => 'rgba(59, 130, 246, 1)',
|
|
'West Mill' => 'rgba(16, 185, 129, 1)',
|
|
'Mill 1' => 'rgba(245, 158, 11, 1)',
|
|
'Mill 2' => 'rgba(239, 68, 68, 1)',
|
|
'Mill 3' => 'rgba(139, 92, 246, 1)',
|
|
'Mill 4' => 'rgba(236, 72, 153, 1)',
|
|
];
|
|
|
|
// Group weekly trends by mill
|
|
$millData = [];
|
|
foreach ($weeklyTrends as $row) {
|
|
$millCode = $row['MillName'];
|
|
$date = formatShortDate($row['BeginningDate']);
|
|
|
|
if (!isset($millData[$millCode])) {
|
|
$millData[$millCode] = [];
|
|
}
|
|
|
|
$millData[$millCode][$date] = $row['TCGround'];
|
|
|
|
if (!in_array($date, $chartLabels)) {
|
|
$chartLabels[] = $date;
|
|
}
|
|
}
|
|
|
|
foreach ($millData as $millCode => $data) {
|
|
$millDisplayName = getMillDisplayName($millNameLookup, $millCode);
|
|
$color = $millColors[$millCode] ?? $millColors[$millDisplayName] ?? 'rgba(107, 114, 128, 1)';
|
|
$bgColor = str_replace(', 1)', ', 0.2)', $color);
|
|
|
|
$values = [];
|
|
foreach ($chartLabels as $label) {
|
|
$values[] = $data[$label] ?? null;
|
|
}
|
|
|
|
$chartDatasets[] = [
|
|
'label' => $millDisplayName,
|
|
'data' => $values,
|
|
'borderColor' => $color,
|
|
'backgroundColor' => $bgColor,
|
|
'tension' => 0.3,
|
|
'fill' => false,
|
|
];
|
|
}
|
|
|
|
// ============================================================================
|
|
// Page Layout
|
|
// ============================================================================
|
|
$pageTitle = 'Mill Data Dashboard';
|
|
$pageSubtitle = 'Analytics & Performance Trends';
|
|
$pageDescription = 'Visual analytics dashboard for mill production data.';
|
|
$layoutWithoutSidebar = true;
|
|
$layoutReturnUrl = '../overview.php';
|
|
$layoutReturnLabel = 'Back to overview';
|
|
$assetBasePath = '../';
|
|
|
|
require __DIR__ . '/../includes/layout/header.php';
|
|
?>
|
|
|
|
<style>
|
|
/* Dashboard Grid */
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
/* KPI Cards */
|
|
.kpi-card {
|
|
background: var(--surface, #1a1a2e);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.kpi-card__label {
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted, #888);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.kpi-card__value {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
color: var(--text, #fff);
|
|
line-height: 1;
|
|
}
|
|
|
|
.kpi-card__sub {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted, #888);
|
|
}
|
|
|
|
.kpi-card--accent {
|
|
border-color: var(--accent, #3b82f6);
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, var(--surface, #1a1a2e) 100%);
|
|
}
|
|
|
|
.kpi-card--success {
|
|
border-color: #10b981;
|
|
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, var(--surface, #1a1a2e) 100%);
|
|
}
|
|
|
|
.kpi-card--warning {
|
|
border-color: #f59e0b;
|
|
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, var(--surface, #1a1a2e) 100%);
|
|
}
|
|
|
|
/* Chart Container */
|
|
.chart-container {
|
|
background: var(--surface, #1a1a2e);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.chart-container__title {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: var(--text, #fff);
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
position: relative;
|
|
height: 300px;
|
|
}
|
|
|
|
/* Mill Performance Cards */
|
|
.mill-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.mill-card {
|
|
background: var(--surface, #1a1a2e);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.mill-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
}
|
|
|
|
.mill-card--east::before { background: #3b82f6; }
|
|
.mill-card--west::before { background: #10b981; }
|
|
.mill-card--default::before { background: #6b7280; }
|
|
|
|
.mill-card__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.mill-card__name {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
.mill-card__date {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted, #888);
|
|
text-align: right;
|
|
}
|
|
|
|
.mill-card__stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.mill-stat {
|
|
text-align: center;
|
|
padding: 0.75rem;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.mill-stat__value {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
.mill-stat__label {
|
|
font-size: 0.7rem;
|
|
color: var(--text-muted, #888);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
/* Sparkline */
|
|
.sparkline-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.75rem 0;
|
|
border-bottom: 1px solid var(--border, #333);
|
|
}
|
|
|
|
.sparkline-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.sparkline-label {
|
|
flex: 0 0 120px;
|
|
font-size: 0.875rem;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
.sparkline-canvas {
|
|
flex: 1;
|
|
height: 30px;
|
|
}
|
|
|
|
.sparkline-value {
|
|
flex: 0 0 80px;
|
|
text-align: right;
|
|
font-weight: 600;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
/* Category Pills */
|
|
.category-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.category-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 20px;
|
|
font-size: 0.8rem;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
.category-pill__count {
|
|
background: var(--accent, #3b82f6);
|
|
color: white;
|
|
padding: 0.125rem 0.5rem;
|
|
border-radius: 10px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Recent Reports Table */
|
|
.recent-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.recent-table th,
|
|
.recent-table td {
|
|
padding: 0.75rem 1rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border, #333);
|
|
}
|
|
|
|
.recent-table th {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--text-muted, #888);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.recent-table td {
|
|
font-size: 0.875rem;
|
|
color: var(--text, #fff);
|
|
}
|
|
|
|
.recent-table tr:hover td {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
}
|
|
|
|
/* Navigation Links */
|
|
.dashboard-nav {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.dashboard-nav__link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1.25rem;
|
|
background: var(--surface, #1a1a2e);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 8px;
|
|
color: var(--text, #fff);
|
|
text-decoration: none;
|
|
font-size: 0.875rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.dashboard-nav__link:hover {
|
|
border-color: var(--accent, #3b82f6);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
color: var(--text, #fff);
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.two-column {
|
|
display: grid;
|
|
grid-template-columns: 2fr 1fr;
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.two-column {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="dashboard-nav">
|
|
<a href="milldata.php" class="dashboard-nav__link">
|
|
📊 Data Viewer
|
|
</a>
|
|
<a href="milldata-compare.php" class="dashboard-nav__link">
|
|
🔀 Compare Mills
|
|
</a>
|
|
</div>
|
|
|
|
<?php if ($connectionError): ?>
|
|
<div class="kpi-card" style="border-color: #ef4444;">
|
|
<div class="kpi-card__label">Connection Error</div>
|
|
<div class="kpi-card__value" style="font-size: 1rem;">Unable to connect to the mill data database.</div>
|
|
</div>
|
|
<?php else: ?>
|
|
|
|
<!-- KPI Cards -->
|
|
<div class="dashboard-grid">
|
|
<div class="kpi-card kpi-card--accent">
|
|
<div class="kpi-card__label">Total Reports</div>
|
|
<div class="kpi-card__value"><?php echo number_format($stats['TotalReports'] ?? 0); ?></div>
|
|
<div class="kpi-card__sub">Across <?php echo number_format($stats['TotalFiles'] ?? 0); ?> files</div>
|
|
</div>
|
|
|
|
<div class="kpi-card kpi-card--success">
|
|
<div class="kpi-card__label">Mills Tracked</div>
|
|
<div class="kpi-card__value"><?php echo number_format($stats['TotalMills'] ?? 0); ?></div>
|
|
<div class="kpi-card__sub">Active production units</div>
|
|
</div>
|
|
|
|
<div class="kpi-card kpi-card--warning">
|
|
<div class="kpi-card__label">Total Metrics</div>
|
|
<div class="kpi-card__value"><?php echo number_format($stats['TotalMetrics'] ?? 0); ?></div>
|
|
<div class="kpi-card__sub">Data points captured</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-card__label">Date Range</div>
|
|
<div class="kpi-card__value" style="font-size: 1.25rem;">
|
|
<?php echo formatDate($stats['EarliestDate']); ?> - <?php echo formatDate($stats['LatestDate']); ?>
|
|
</div>
|
|
<div class="kpi-card__sub">Report coverage period</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mill Performance Cards -->
|
|
<?php if (!empty($millPerformance)): ?>
|
|
<h2 class="section-title">📈 Latest Mill Performance</h2>
|
|
<div class="mill-cards">
|
|
<?php foreach ($millPerformance as $mill): ?>
|
|
<?php
|
|
$cardClass = 'mill-card--default';
|
|
$millCode = $mill['MillName'];
|
|
$millDisplayName = getMillDisplayName($millNameLookup, $millCode);
|
|
if (stripos($millCode, 'east') !== false) $cardClass = 'mill-card--east';
|
|
if (stripos($millCode, 'west') !== false) $cardClass = 'mill-card--west';
|
|
?>
|
|
<div class="mill-card <?php echo $cardClass; ?>">
|
|
<div class="mill-card__header">
|
|
<div class="mill-card__name"><?php echo htmlspecialchars($millDisplayName); ?></div>
|
|
<div class="mill-card__date">
|
|
Week of<br>
|
|
<?php echo formatDate($mill['BeginningDate']); ?>
|
|
</div>
|
|
</div>
|
|
<div class="mill-card__stats">
|
|
<div class="mill-stat">
|
|
<div class="mill-stat__value">
|
|
<?php echo $mill['TCGround'] !== null ? number_format($mill['TCGround'], 0) : '-'; ?>
|
|
</div>
|
|
<div class="mill-stat__label">TC Ground</div>
|
|
</div>
|
|
<div class="mill-stat">
|
|
<div class="mill-stat__value">
|
|
<?php echo $mill['HoursRun'] !== null ? number_format($mill['HoursRun'], 1) : '-'; ?>
|
|
</div>
|
|
<div class="mill-stat__label">Hours Run</div>
|
|
</div>
|
|
<div class="mill-stat">
|
|
<div class="mill-stat__value">
|
|
<?php echo $mill['LostTime'] !== null ? number_format($mill['LostTime'], 1) : '-'; ?>
|
|
</div>
|
|
<div class="mill-stat__label">Lost Time</div>
|
|
</div>
|
|
<div class="mill-stat">
|
|
<div class="mill-stat__value">
|
|
<?php echo $mill['TCPerHour'] !== null ? number_format($mill['TCPerHour'], 1) : '-'; ?>
|
|
</div>
|
|
<div class="mill-stat__label">TC/Hour</div>
|
|
</div>
|
|
</div>
|
|
<?php if ($mill['TCToDate'] !== null): ?>
|
|
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border, #333); text-align: center;">
|
|
<span style="color: var(--text-muted, #888); font-size: 0.75rem;">Season Total:</span>
|
|
<span style="font-weight: 700; font-size: 1.25rem; margin-left: 0.5rem;">
|
|
<?php echo number_format($mill['TCToDate'], 0); ?> TC
|
|
</span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Charts Section -->
|
|
<div class="two-column">
|
|
<div class="chart-container">
|
|
<div class="chart-container__title">📊 Weekly TC Ground Trend</div>
|
|
<div class="chart-wrapper">
|
|
<canvas id="tcTrendChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<div class="chart-container__title">📁 Data Categories</div>
|
|
<?php if (!empty($categoryBreakdown)): ?>
|
|
<div class="category-list">
|
|
<?php foreach ($categoryBreakdown as $cat): ?>
|
|
<div class="category-pill">
|
|
<?php echo htmlspecialchars($cat['Category']); ?>
|
|
<span class="category-pill__count"><?php echo $cat['MetricCount']; ?></span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<p style="color: var(--text-muted, #888);">No category data available.</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Reports -->
|
|
<div class="chart-container">
|
|
<div class="chart-container__title">🕐 Recent Reports</div>
|
|
<?php if (!empty($recentReports)): ?>
|
|
<div style="overflow-x: auto;">
|
|
<table class="recent-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Mill</th>
|
|
<th>Source File</th>
|
|
<th>Begin Date</th>
|
|
<th>End Date</th>
|
|
<th>Crop Days</th>
|
|
<th>Processed</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($recentReports as $report): ?>
|
|
<tr>
|
|
<td><strong><?php echo htmlspecialchars(getMillDisplayName($millNameLookup, $report['MillName'])); ?></strong></td>
|
|
<td style="font-size: 0.75rem; color: var(--text-muted, #888);">
|
|
<?php echo htmlspecialchars($report['SourceFileName']); ?>
|
|
</td>
|
|
<td><?php echo formatDate($report['BeginningDate']); ?></td>
|
|
<td><?php echo formatDate($report['EndingDate']); ?></td>
|
|
<td><?php echo $report['CropDays'] ?? '-'; ?></td>
|
|
<td style="font-size: 0.75rem; color: var(--text-muted, #888);">
|
|
<?php echo formatDate($report['ProcessedAt']); ?>
|
|
</td>
|
|
<td>
|
|
<a href="milldata.php?report=<?php echo $report['ReportId']; ?>&file=<?php echo urlencode($report['SourceFileName']); ?>"
|
|
style="color: var(--accent, #3b82f6); text-decoration: none; font-size: 0.8rem;">
|
|
View →
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php else: ?>
|
|
<p style="color: var(--text-muted, #888);">No reports available.</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php endif; ?>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const chartLabels = <?php echo json_encode($chartLabels); ?>;
|
|
const chartDatasets = <?php echo json_encode($chartDatasets); ?>;
|
|
|
|
if (chartLabels.length > 0 && chartDatasets.length > 0) {
|
|
const ctx = document.getElementById('tcTrendChart');
|
|
|
|
if (ctx) {
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: chartLabels,
|
|
datasets: chartDatasets
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: {
|
|
color: '#888',
|
|
usePointStyle: true,
|
|
padding: 20
|
|
}
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#fff',
|
|
borderColor: '#333',
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
callbacks: {
|
|
label: function(context) {
|
|
let label = context.dataset.label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if (context.parsed.y !== null) {
|
|
label += new Intl.NumberFormat().format(context.parsed.y) + ' TC';
|
|
}
|
|
return label;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
color: 'rgba(255, 255, 255, 0.05)'
|
|
},
|
|
ticks: {
|
|
color: '#888'
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(255, 255, 255, 0.05)'
|
|
},
|
|
ticks: {
|
|
color: '#888',
|
|
callback: function(value) {
|
|
return new Intl.NumberFormat().format(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/../includes/layout/footer.php'; ?>
|