296 lines
13 KiB
PHP
296 lines
13 KiB
PHP
<div>
|
|
<div class="mb-6">
|
|
<h2 class="text-xl font-bold mb-4">Reportes y Analítica</h2>
|
|
<div class="mb-4">\n <div class="flex space-x-3">\n <a href="{{ route("reports.export.projects") }}"\n class="btn btn-success btn-sm flex items-center">\n <i class="fas fa-file-excel mr-1"></i> Exportar Proyectos\n </a>\n <a href="{{ route("reports.export.phases") }}"\n class="btn btn-info btn-sm flex items-center">\n <i class="fas fa-file-excel mr-1"></i> Exportar Fases\n </a>\n <a href="{{ route("reports.export.inspections") }}"\n class="btn btn-warning btn-sm flex items-center">\n <i class="fas fa-file-excel mr-1"></i> Exportar Inspecciones\n </a>\n </div>\n </div>
|
|
|
|
<div class="flex flex-wrap gap-4 mb-6">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm font-medium">Rango de tiempo:</span>
|
|
<select wire:model="dateRange" class="border border-gray-300 rounded px-3 py-1">
|
|
<option value="week">Esta semana</option>
|
|
<option value="month" selected>Este mes</option>
|
|
<option value="quarter">Este trimestre</option>
|
|
<option value="year">Este año</option>
|
|
</select>
|
|
</div>
|
|
|
|
<button wire:click="loadChartData"
|
|
class="btn btn-primary btn-sm">
|
|
Actualizar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if(isset($chartData['months']))
|
|
<div class="grid gap-6 mb-8">
|
|
{{-- Gráfico de progreso de proyectos --}}
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h3 class="text-lg font-semibold mb-4">Progreso de Proyectos (últimos 6 meses)</h3>
|
|
<div style="position: relative; height: 300px;">
|
|
<canvas id="projectProgressChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Gráfico de inspecciones por tipo --}}
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h3 class="text-lg font-semibold mb-4">Inspecciones por Tipo</h3>
|
|
<div style="position: relative; height: 300px;">
|
|
<canvas id="inspectionTypesChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Gráfico de proyectos por estado --}}
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h3 class="text-lg font-semibold mb-4">Distribución de Proyectos por Estado</h3>
|
|
<div style="position: relative; height: 300px;">
|
|
<canvas id="projectsByStatusChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Gráfico de progreso promedio por proyecto --}}
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h3 class="text-lg font-semibold mb-4">Progreso Promedio por Proyecto</h3>
|
|
<div style="position: relative; height: 300px;">
|
|
<canvas id="projectPhaseProgressChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Tarjetas de métricas clave --}}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<div class="text-sm text-gray-500 uppercase tracking-wide mb-2">
|
|
Total Proyectos Activos
|
|
</div>
|
|
<div class="text-2xl font-bold">
|
|
{{ \App\Models\Project::where('status', 'in_progress')->count() }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<div class="text-sm text-gray-500 uppercase tracking-wide mb-2">
|
|
Inspecciones Este Mes
|
|
</div>
|
|
<div class="text-2xl font-bold">
|
|
{{ \App\Models\Inspection::whereDate('created_at', '>=', now()->startOfMonth())->count() }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<div class="text-sm text-gray-500 uppercase tracking-wide mb-2">
|
|
Promedio de Progreso
|
|
</div>
|
|
<div class="text-2xl font-bold">
|
|
@php
|
|
$avgProgress = \App\Models\Phase::whereNotNull('progress_percent')
|
|
->avg('progress_percent') ?? 0;
|
|
@endphp
|
|
{{ number_format($avgProgress, 1) }}%
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<div class="text-sm text-gray-500 uppercase tracking-wide mb-2">
|
|
Proyectos Completados
|
|
</div>
|
|
<div class="text-2xl font-bold">
|
|
{{ \App\Models\Project::where('status', 'completed')->count() }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="bg-white rounded-lg shadow p-6 text-center">
|
|
<p class="text-gray-500">Cargando datos...</p>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<script>
|
|
// Wait for Alpine to initialize
|
|
document.addEventListener('alpine:init', () => {
|
|
// Initialize charts when data is available
|
|
window.addEventListener('livewire:load', function() {
|
|
initializeCharts();
|
|
});
|
|
|
|
window.addEventListener('livewire:updated', function() {
|
|
initializeCharts();
|
|
});
|
|
|
|
function initializeCharts() {
|
|
if (typeof Chart === 'undefined') {
|
|
console.warn('Chart.js not loaded');
|
|
return;
|
|
}
|
|
|
|
// Destroy existing charts if they exist
|
|
const chartIds = ['projectProgressChart', 'inspectionTypesChart', 'projectsByStatusChart', 'projectPhaseProgressChart'];
|
|
chartIds.forEach(id => {
|
|
const ctx = document.getElementById(id);
|
|
if (ctx) {
|
|
// Check if chart instance exists and destroy it
|
|
if (ctx.chart instanceof Chart) {
|
|
ctx.chart.destroy();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Project Progress Chart (Line)
|
|
const projectProgressCtx = document.getElementById('projectProgressChart');
|
|
if (projectProgressCtx) {
|
|
new Chart(projectProgressCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: @json($chartData['months'] ?? []),
|
|
datasets: @json($chartData['projectProgress'] ?? [])
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
title: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100,
|
|
title: {
|
|
display: true,
|
|
text: 'Progreso (%)'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Inspection Types Chart (Bar)
|
|
const inspectionTypesCtx = document.getElementById('inspectionTypesChart');
|
|
if (inspectionTypesCtx) {
|
|
new Chart(inspectionTypesCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: @json($chartData['inspectionTypes']['labels'] ?? []),
|
|
datasets: [{
|
|
label: 'Cantidad de inspecciones',
|
|
data: @json($chartData['inspectionTypes']['data'] ?? []),
|
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
|
borderColor: 'rgba(54, 162, 235, 1)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
title: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: 'Cantidad'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Projects by Status Chart (Pie/Doughnut)
|
|
const projectsByStatusCtx = document.getElementById('projectsByStatusChart');
|
|
if (projectsByStatusCtx) {
|
|
new Chart(projectsByStatusCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: @json($chartData['projectsByStatus']['labels'] ?? []),
|
|
datasets: [{
|
|
label: 'Proyectos por estado',
|
|
data: @json($chartData['projectsByStatus']['data'] ?? []),
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.5)',
|
|
'rgba(54, 162, 235, 0.5)',
|
|
'rgba(255, 206, 86, 0.5)',
|
|
'rgba(75, 192, 192, 0.5)',
|
|
'rgba(153, 102, 255, 0.5)',
|
|
'rgba(255, 159, 64, 0.5)'
|
|
],
|
|
borderColor: [
|
|
'rgba(255, 99, 132, 1)',
|
|
'rgba(54, 162, 235, 1)',
|
|
'rgba(255, 206, 86, 1)',
|
|
'rgba(75, 192, 192, 1)',
|
|
'rgba(153, 102, 255, 1)',
|
|
'rgba(255, 159, 64, 1)'
|
|
],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
title: {
|
|
display: false
|
|
},
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Project Phase Progress Chart (Bar - Horizontal)
|
|
const projectPhaseProgressCtx = document.getElementById('projectPhaseProgressChart');
|
|
if (projectPhaseProgressCtx) {
|
|
// Sort by progress descending
|
|
const sortedData = (@json($chartData['projectPhaseProgress'] ?? [])).sort((a, b) => b.progress - a.progress);
|
|
|
|
new Chart(projectPhaseProgressCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: sortedData.map(item => item.name),
|
|
datasets: [{
|
|
label: 'Progreso promedio (%)',
|
|
data: sortedData.map(item => item.progress),
|
|
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
|
borderColor: 'rgba(75, 192, 192, 1)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
indexAxis: 'y', // Horizontal bars
|
|
plugins: {
|
|
title: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
max: 100,
|
|
title: {
|
|
display: true,
|
|
text: 'Progreso (%)'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
</script>
|