Files
MokoSuiteCross/source/packages/com_mokosuitecross/tmpl/dashboard/default.php
T
jmiller e67fbdfe2b
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
feat: add visual post calendar with drag-drop rescheduling (#160)
Authored-by: Moko Consulting
2026-06-28 11:51:05 -05:00

294 lines
15 KiB
PHP

<?php
/**
* @package MokoSuiteCross
* @subpackage com_mokosuitecross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\Component\MokoSuiteCross\Administrator\Helper\ServiceIconHelper;
/** @var \Joomla\Component\MokoSuiteCross\Administrator\View\Dashboard\HtmlView $this */
$stats = $this->stats;
$componentParams = ComponentHelper::getParams('com_mokosuitecross');
$queueProcessing = $componentParams->get('queue_processing', 'scheduler');
?>
<?php if ($queueProcessing === 'pageload' || $queueProcessing === 'both') : ?>
<div class="alert alert-warning d-flex align-items-start mb-3">
<span class="icon-exclamation-triangle me-2 mt-1" aria-hidden="true"></span>
<div>
<strong><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_PAGELOAD_WARNING_TITLE'); ?></strong><br>
<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_PAGELOAD_WARNING'); ?>
</div>
</div>
<?php endif; ?>
<?php if ($stats->queued_count > 50) : ?>
<div class="alert alert-warning d-flex align-items-start mb-3">
<span class="icon-exclamation-triangle me-2 mt-1" aria-hidden="true"></span>
<div>
<strong><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING_TITLE'); ?></strong><br>
<?php echo Text::sprintf('COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING', $stats->queued_count); ?>
</div>
</div>
<?php endif; ?>
<div class="row">
<div class="col-lg-9">
<div class="row">
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_ACTIVE_SERVICES'); ?></h5>
<p class="display-4"><?php echo $stats->active_services; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUED'); ?></h5>
<p class="display-4 text-warning"><?php echo $stats->queued_count; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED'); ?></h5>
<p class="display-4 text-success"><?php echo $stats->posted_count; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED'); ?></h5>
<p class="display-4 text-danger"><?php echo $stats->failed_count; ?></p>
</div>
</div>
</div>
</div>
<!-- Trend Chart -->
<?php if (!empty($this->dailyTrend)) : ?>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TREND_CHART'); ?></h5>
<form method="get" class="d-inline">
<input type="hidden" name="option" value="com_mokosuitecross" />
<input type="hidden" name="view" value="dashboard" />
<select name="period" class="form-select form-select-sm" style="width: auto; display: inline-block;" onchange="this.form.submit();">
<option value="7" <?php echo $this->period == 7 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_7_DAYS'); ?></option>
<option value="30" <?php echo $this->period == 30 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_30_DAYS'); ?></option>
<option value="90" <?php echo $this->period == 90 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_90_DAYS'); ?></option>
<option value="0" <?php echo $this->period == 0 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_ALL_TIME'); ?></option>
</select>
</form>
</div>
<div class="card-body">
<canvas id="trendChart" height="80"></canvas>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js" integrity="sha384-UPIssOjNMqMfON6mDKHvO4sOY4hhxN1ymYcfl2MrDz69idMU/L3MNFlyJGlIRjQH" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var trendData = <?php echo json_encode($this->dailyTrend); ?>;
var labels = trendData.map(function(d) { return d.day; });
var posted = trendData.map(function(d) { return parseInt(d.posted, 10); });
var failed = trendData.map(function(d) { return parseInt(d.failed, 10); });
new Chart(document.getElementById('trendChart'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED', true); ?>',
data: posted,
borderColor: '#198754',
backgroundColor: 'rgba(25, 135, 84, 0.1)',
fill: true,
tension: 0.3
},
{
label: '<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED', true); ?>',
data: failed,
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
fill: true,
tension: 0.3
}
]
},
options: {
responsive: true,
scales: {
y: { beginAtZero: true, ticks: { stepSize: 1 } }
},
plugins: {
legend: { position: 'bottom' }
}
}
});
});
</script>
<?php endif; ?>
<?php if ($this->migrationAvailable) : ?>
<div class="alert alert-info">
<h4 class="alert-heading"><?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_TITLE'); ?></h4>
<p><?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_DESCRIPTION'); ?></p>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&task=dashboard.migrate&' . \Joomla\CMS\Session\Session::getFormToken() . '=1'); ?>"
class="btn btn-primary">
<?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_BUTTON'); ?>
</a>
</div>
<?php endif; ?>
<!-- Analytics: Service Breakdown -->
<?php if (!empty($this->serviceBreakdown)) : ?>
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_SERVICE_BREAKDOWN'); ?></h5>
</div>
<div class="card-body">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th><?php echo Text::_('COM_MOKOSUITECROSS_HEADING_SERVICE'); ?></th>
<th class="text-center text-success"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED'); ?></th>
<th class="text-center text-danger"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED'); ?></th>
<th class="text-center text-warning"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUED'); ?></th>
<th class="text-center"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TOTAL_POSTS'); ?></th>
<th class="text-center"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_SUCCESS_RATE'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->serviceBreakdown as $row) :
$rate = $row['total'] > 0 ? round(($row['posted'] / $row['total']) * 100) : 0;
$rateClass = $rate >= 80 ? 'text-success' : ($rate >= 50 ? 'text-warning' : 'text-danger');
?>
<tr>
<td>
<?php echo ServiceIconHelper::renderIcon($row['service_type']); ?>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=servicestats&id=' . $row['service_id']); ?>">
<?php echo htmlspecialchars($row['service_title'] . ' (' . ucfirst($row['service_type']) . ')'); ?>
</a>
</td>
<td class="text-center"><span class="badge bg-success"><?php echo (int) $row['posted']; ?></span></td>
<td class="text-center"><span class="badge bg-danger"><?php echo (int) $row['failed']; ?></span></td>
<td class="text-center"><span class="badge bg-warning text-dark"><?php echo (int) $row['queued']; ?></span></td>
<td class="text-center"><?php echo (int) $row['total']; ?></td>
<td class="text-center <?php echo $rateClass; ?> fw-bold"><?php echo $rate; ?>%</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<!-- Analytics: Top Articles -->
<?php if (!empty($this->topArticles)) : ?>
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TOP_ARTICLES'); ?></h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<?php foreach ($this->topArticles as $row) : ?>
<div class="list-group-item d-flex justify-content-between align-items-center">
<span><?php echo htmlspecialchars($row['title']); ?></span>
<span>
<span class="badge bg-success"><?php echo (int) $row['success_count']; ?></span>
/
<span class="badge bg-secondary"><?php echo (int) $row['post_count']; ?></span>
</span>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- Recent Activity -->
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_RECENT_ACTIVITY'); ?></h5>
</div>
<div class="card-body p-0">
<?php if (empty($this->recentActivity)) : ?>
<p class="p-3 mb-0 text-muted"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_NO_RECENT'); ?></p>
<?php else : ?>
<div class="list-group list-group-flush">
<?php foreach ($this->recentActivity as $entry) :
$levelClass = match ($entry->level) {
'error' => 'text-danger',
'warning' => 'text-warning',
default => 'text-muted',
};
$levelIcon = match ($entry->level) {
'error' => 'icon-times-circle',
'warning' => 'icon-exclamation-triangle',
default => 'icon-info-circle',
};
?>
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<span class="<?php echo $levelClass; ?>">
<span class="<?php echo $levelIcon; ?>" aria-hidden="true"></span>
<?php echo htmlspecialchars(mb_substr($entry->message, 0, 120)); ?>
</span>
<small class="text-muted"><?php echo \Joomla\CMS\HTML\HTMLHelper::_('date', $entry->created, 'Y-m-d H:i'); ?></small>
</div>
<?php if ($entry->service_title) : ?>
<small class="text-muted"><?php echo htmlspecialchars($entry->service_title); ?></small>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="card">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUICK_LINKS'); ?></h5>
<div class="list-group list-group-flush">
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=services'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_SERVICES'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=posts'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_POSTS'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=templates'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_TEMPLATES'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=logs'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_LOGS'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=calendar'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOSUITECROSS_CALENDAR'); ?>
</a>
</div>
</div>
</div>
</div>
</div>