Files
desk-moloni/modules/desk_moloni/views/admin/dashboard.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:37 +01:00

710 lines
29 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<div class="row">
<div class="col-md-12">
<div class="panel_s">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<h4 class="no-margin">
<i class="fa-regular fa-chart-bar" aria-hidden="true"></i>
<?php echo _l('desk_moloni_dashboard'); ?>
</h4>
</div>
<div class="col-md-6">
<div class="text-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-filter"></i> <?php echo _l('filter'); ?> <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="#" data-filter="7"><?php echo _l('desk_moloni_last_7_days'); ?></a></li>
<li><a href="#" data-filter="30"><?php echo _l('desk_moloni_last_30_days'); ?></a></li>
<li><a href="#" data-filter="90"><?php echo _l('desk_moloni_last_90_days'); ?></a></li>
</ul>
</div>
<button type="button" class="btn btn-info" id="refresh-dashboard">
<i class="fa fa-refresh"></i> <?php echo _l('refresh'); ?>
</button>
<?php if (has_permission('desk_moloni_config', '', 'edit')) { ?>
<a href="<?php echo admin_url('modules/desk_moloni/config'); ?>" class="btn btn-primary">
<i class="fa fa-cog"></i> <?php echo _l('settings'); ?>
</a>
<?php } ?>
</div>
</div>
</div>
<hr class="hr-panel-separator" />
<!-- Status Cards -->
<div class="row" id="status-cards">
<div class="col-lg-3 col-md-6">
<div class="panel panel-info">
<div class="panel-body">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-check-circle fa-3x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge" id="successful-syncs">-</div>
<div><?php echo _l('desk_moloni_successful_syncs'); ?></div>
</div>
</div>
</div>
<div class="panel-footer">
<span class="pull-left" id="success-rate">-</span>
<span class="pull-right">
<i class="fa fa-arrow-circle-right"></i>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-warning">
<div class="panel-body">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-clock-o fa-3x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge" id="pending-tasks">-</div>
<div><?php echo _l('desk_moloni_pending_tasks'); ?></div>
</div>
</div>
</div>
<div class="panel-footer">
<span class="pull-left"><?php echo _l('desk_moloni_in_queue'); ?></span>
<span class="pull-right">
<i class="fa fa-arrow-circle-right"></i>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-danger">
<div class="panel-body">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-exclamation-triangle fa-3x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge" id="failed-syncs">-</div>
<div><?php echo _l('desk_moloni_failed_syncs'); ?></div>
</div>
</div>
</div>
<div class="panel-footer">
<span class="pull-left" id="error-rate">-</span>
<span class="pull-right">
<i class="fa fa-arrow-circle-right"></i>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-success">
<div class="panel-body">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-tachometer fa-3x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge" id="sync-rate">-</div>
<div><?php echo _l('desk_moloni_sync_rate_24h'); ?></div>
</div>
</div>
</div>
<div class="panel-footer">
<span class="pull-left"><?php echo _l('desk_moloni_per_hour'); ?></span>
<span class="pull-right">
<i class="fa fa-arrow-circle-right"></i>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<!-- Main Charts Row -->
<div class="row">
<div class="col-md-8">
<div class="panel_s">
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<h4><?php echo _l('desk_moloni_sync_volume_chart'); ?></h4>
<canvas id="syncVolumeChart" height="100"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel_s">
<div class="panel-body">
<h4><?php echo _l('desk_moloni_entity_distribution'); ?></h4>
<canvas id="entityDistributionChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Second Row -->
<div class="row">
<div class="col-md-6">
<div class="panel_s">
<div class="panel-body">
<h4><?php echo _l('desk_moloni_success_rate_trend'); ?></h4>
<canvas id="successRateChart" height="150"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel_s">
<div class="panel-body">
<h4><?php echo _l('desk_moloni_performance_metrics'); ?></h4>
<canvas id="performanceChart" height="150"></canvas>
</div>
</div>
</div>
</div>
<!-- Activity Feed and Errors -->
<div class="row">
<div class="col-md-8">
<div class="panel_s">
<div class="panel-body">
<div class="row">
<div class="col-md-8">
<h4><?php echo _l('desk_moloni_recent_activity'); ?></h4>
</div>
<div class="col-md-4 text-right">
<a href="<?php echo admin_url('modules/desk_moloni/logs'); ?>" class="btn btn-default btn-sm">
<?php echo _l('desk_moloni_view_all_logs'); ?>
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover" id="recent-activity-table">
<thead>
<tr>
<th><?php echo _l('desk_moloni_timestamp'); ?></th>
<th><?php echo _l('desk_moloni_entity'); ?></th>
<th><?php echo _l('desk_moloni_operation'); ?></th>
<th><?php echo _l('desk_moloni_status'); ?></th>
<th><?php echo _l('desk_moloni_duration'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="5" class="text-center">
<i class="fa fa-spinner fa-spin"></i> <?php echo _l('loading'); ?>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel_s">
<div class="panel-body">
<div class="row">
<div class="col-md-8">
<h4><?php echo _l('desk_moloni_recent_errors'); ?></h4>
</div>
<div class="col-md-4 text-right">
<a href="<?php echo admin_url('modules/desk_moloni/logs?status=error'); ?>" class="btn btn-default btn-sm">
<?php echo _l('desk_moloni_view_all'); ?>
</a>
</div>
</div>
<div id="recent-errors-container">
<div class="text-center">
<i class="fa fa-spinner fa-spin"></i> <?php echo _l('loading'); ?>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Configuration Status -->
<div class="row">
<div class="col-md-12">
<div class="panel_s">
<div class="panel-body">
<h4><?php echo _l('desk_moloni_system_status'); ?></h4>
<div class="row">
<div class="col-md-3">
<div class="text-center">
<div id="oauth-status" class="status-indicator">
<i class="fa fa-question-circle fa-2x text-muted"></i>
<p><?php echo _l('desk_moloni_oauth_status'); ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div id="sync-status" class="status-indicator">
<i class="fa fa-question-circle fa-2x text-muted"></i>
<p><?php echo _l('desk_moloni_sync_status'); ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div id="queue-status" class="status-indicator">
<i class="fa fa-question-circle fa-2x text-muted"></i>
<p><?php echo _l('desk_moloni_queue_status'); ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div id="api-status" class="status-indicator">
<i class="fa fa-question-circle fa-2x text-muted"></i>
<p><?php echo _l('desk_moloni_api_status'); ?></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Modal -->
<div class="modal fade" id="loading-modal" tabindex="-1" role="dialog" aria-labelledby="loadingModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p class="mt-3"><?php echo _l('desk_moloni_loading'); ?></p>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Global dashboard variables
var dashboardData = {};
var charts = {};
var currentFilter = 7;
var refreshInterval;
// Initialize dashboard
initializeDashboard();
// Event handlers
$('#refresh-dashboard').on('click', function() {
refreshDashboard();
});
$('[data-filter]').on('click', function(e) {
e.preventDefault();
currentFilter = $(this).data('filter');
refreshDashboard();
});
function initializeDashboard() {
showLoadingModal();
loadDashboardData();
startAutoRefresh();
}
function loadDashboardData() {
$.ajax({
url: admin_url + 'modules/desk_moloni/get_status',
type: 'GET',
data: {
days: currentFilter
},
dataType: 'json',
success: function(response) {
if (response.success) {
dashboardData = response.data;
updateStatusCards();
updateCharts();
updateActivity();
updateSystemStatus();
} else {
showError(response.message);
}
hideLoadingModal();
},
error: function(xhr, status, error) {
showError('<?php echo _l("desk_moloni_dashboard_load_error"); ?>');
hideLoadingModal();
}
});
}
function refreshDashboard() {
loadDashboardData();
}
function updateStatusCards() {
var stats = dashboardData.sync_stats;
$('#successful-syncs').text(numberFormat(stats.successful_syncs || 0));
$('#pending-tasks').text(numberFormat(stats.pending_tasks || 0));
$('#failed-syncs').text(numberFormat(stats.failed_syncs || 0));
$('#sync-rate').text(numberFormat(stats.sync_rate_24h || 0));
var successRate = stats.total_synced > 0 ?
((stats.successful_syncs / stats.total_synced) * 100).toFixed(1) + '%' : '0%';
$('#success-rate').text('<?php echo _l("desk_moloni_success_rate"); ?>: ' + successRate);
var errorRate = stats.total_synced > 0 ?
((stats.failed_syncs / stats.total_synced) * 100).toFixed(1) + '%' : '0%';
$('#error-rate').text('<?php echo _l("desk_moloni_error_rate"); ?>: ' + errorRate);
}
function updateCharts() {
// Load chart data and update charts
$.ajax({
url: admin_url + 'modules/desk_moloni/dashboard/get_analytics',
type: 'GET',
data: {
days: currentFilter
},
dataType: 'json',
success: function(response) {
if (response.success) {
updateSyncVolumeChart(response.data.charts.sync_volume_chart);
updateEntityDistributionChart(response.data.charts.entity_distribution);
updateSuccessRateChart(response.data.charts.success_rate_chart);
updatePerformanceChart(response.data.charts.performance_chart);
}
}
});
}
function updateSyncVolumeChart(data) {
var ctx = document.getElementById('syncVolumeChart').getContext('2d');
if (charts.syncVolume) {
charts.syncVolume.destroy();
}
charts.syncVolume = new Chart(ctx, {
type: 'line',
data: {
labels: data.labels || [],
datasets: [{
label: '<?php echo _l("desk_moloni_successful_syncs"); ?>',
data: data.successful || [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1
}, {
label: '<?php echo _l("desk_moloni_failed_syncs"); ?>',
data: data.failed || [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function updateEntityDistributionChart(data) {
var ctx = document.getElementById('entityDistributionChart').getContext('2d');
if (charts.entityDistribution) {
charts.entityDistribution.destroy();
}
charts.entityDistribution = new Chart(ctx, {
type: 'doughnut',
data: {
labels: data.labels || [],
datasets: [{
data: data.values || [],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 205, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(153, 102, 255, 0.8)'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
function updateSuccessRateChart(data) {
var ctx = document.getElementById('successRateChart').getContext('2d');
if (charts.successRate) {
charts.successRate.destroy();
}
charts.successRate = new Chart(ctx, {
type: 'line',
data: {
labels: data.labels || [],
datasets: [{
label: '<?php echo _l("desk_moloni_success_rate"); ?> (%)',
data: data.values || [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
}
});
}
function updatePerformanceChart(data) {
var ctx = document.getElementById('performanceChart').getContext('2d');
if (charts.performance) {
charts.performance.destroy();
}
charts.performance = new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels || [],
datasets: [{
label: '<?php echo _l("desk_moloni_avg_execution_time"); ?> (ms)',
data: data.values || [],
backgroundColor: 'rgba(153, 102, 255, 0.8)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function updateActivity() {
var tbody = $('#recent-activity-table tbody');
tbody.empty();
if (dashboardData.recent_activity && dashboardData.recent_activity.length > 0) {
$.each(dashboardData.recent_activity.slice(0, 10), function(index, activity) {
var statusClass = getStatusClass(activity.status);
var statusIcon = getStatusIcon(activity.status);
var row = '<tr>' +
'<td>' + formatDateTime(activity.created_at) + '</td>' +
'<td>' + activity.entity_type + ' #' + activity.entity_id + '</td>' +
'<td>' + activity.operation_type + '</td>' +
'<td><span class="label label-' + statusClass + '"><i class="fa ' + statusIcon + '"></i> ' + activity.status + '</span></td>' +
'<td>' + (activity.execution_time_ms || 0) + 'ms</td>' +
'</tr>';
tbody.append(row);
});
} else {
tbody.append('<tr><td colspan="5" class="text-center"><?php echo _l("desk_moloni_no_recent_activity"); ?></td></tr>');
}
}
function updateSystemStatus() {
// OAuth Status
var oauthIcon = dashboardData.oauth_configured ?
'<i class="fa fa-check-circle fa-2x text-success"></i>' :
'<i class="fa fa-exclamation-triangle fa-2x text-warning"></i>';
$('#oauth-status').html(oauthIcon + '<p><?php echo _l("desk_moloni_oauth_status"); ?></p>');
// Sync Status
var syncIcon = dashboardData.sync_enabled ?
'<i class="fa fa-play-circle fa-2x text-success"></i>' :
'<i class="fa fa-pause-circle fa-2x text-warning"></i>';
$('#sync-status').html(syncIcon + '<p><?php echo _l("desk_moloni_sync_status"); ?></p>');
// Queue Status
var queueIcon = dashboardData.queue_status.processing_tasks > 0 ?
'<i class="fa fa-cog fa-spin fa-2x text-info"></i>' :
'<i class="fa fa-check-circle fa-2x text-success"></i>';
$('#queue-status').html(queueIcon + '<p><?php echo _l("desk_moloni_queue_status"); ?></p>');
// API Status (will be updated via separate call)
updateApiStatus();
}
function updateApiStatus() {
$.ajax({
url: admin_url + 'modules/desk_moloni/test_connection',
type: 'POST',
dataType: 'json',
success: function(response) {
var apiIcon = response.success ?
'<i class="fa fa-check-circle fa-2x text-success"></i>' :
'<i class="fa fa-exclamation-triangle fa-2x text-danger"></i>';
$('#api-status').html(apiIcon + '<p><?php echo _l("desk_moloni_api_status"); ?></p>');
},
error: function() {
$('#api-status').html('<i class="fa fa-exclamation-triangle fa-2x text-danger"></i><p><?php echo _l("desk_moloni_api_status"); ?></p>');
}
});
}
function startAutoRefresh() {
refreshInterval = setInterval(function() {
loadDashboardData();
}, 30000); // Refresh every 30 seconds
}
function stopAutoRefresh() {
if (refreshInterval) {
clearInterval(refreshInterval);
}
}
function showLoadingModal() {
$('#loading-modal').modal('show');
}
function hideLoadingModal() {
$('#loading-modal').modal('hide');
}
function showError(message) {
alert_float('danger', message);
}
function numberFormat(num) {
return new Intl.NumberFormat().format(num);
}
function formatDateTime(dateString) {
var date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
}
function getStatusClass(status) {
switch (status) {
case 'success': return 'success';
case 'error': return 'danger';
case 'warning': return 'warning';
default: return 'default';
}
}
function getStatusIcon(status) {
switch (status) {
case 'success': return 'fa-check';
case 'error': return 'fa-exclamation-triangle';
case 'warning': return 'fa-exclamation-circle';
default: return 'fa-info-circle';
}
}
// Cleanup on page unload
$(window).on('beforeunload', function() {
stopAutoRefresh();
// Destroy all charts
Object.keys(charts).forEach(function(key) {
if (charts[key]) {
charts[key].destroy();
}
});
});
});
</script>
<style>
.status-indicator {
padding: 15px;
border-radius: 8px;
background-color: #f8f9fa;
margin-bottom: 10px;
}
.status-indicator i {
display: block;
margin-bottom: 10px;
}
.status-indicator p {
margin: 0;
font-size: 12px;
color: #666;
}
.huge {
font-size: 40px;
}
.panel-footer {
background-color: transparent;
border-top: 1px solid #e7e7e7;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
#recent-activity-table .label {
font-size: 11px;
}
.dashboard-loading {
text-align: center;
padding: 50px;
color: #999;
}
.dashboard-loading i {
font-size: 24px;
margin-bottom: 10px;
}
</style>