Files
javier 7d854ffb0a feat: i18n, language switcher fix, DataTable improvements, blade translations
- Translation system: lang/es/ PHP files (auth, validation, pagination, passwords)
- Rappasoft vendor translations published (lang/vendor/livewire-tables/es/)
- JSON files synced to 391 keys (EN + ES, full parity)
- APP_LOCALE changed to 'es', users.locale column default changed to 'es'
- Language switcher fixed: JS event + window.location.reload() avoids /livewire/update redirect
- SetLocale middleware fallback uses config('app.locale') instead of hardcoded 'en'
- setSortingPillsEnabled(false) on ProjectTable, CompanyTable, UserTable
- Translated 17 blade views: project-map, template-manager, layer-manager,
  company-management, phase-list, media-manager, reports-dashboard,
  client-projects, layer-upload, project-form, project-map-editor-tab,
  admin/users, projects/media, projects/templates, layouts/client
- Navigation 'Empresas' link uses __('Companies')
- Fixed typo key 'Fases and layers' -> 'Phases and layers'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 18:05:53 +02:00

296 lines
13 KiB
PHP

<div>
<div class="mb-6">
<h2 class="text-xl font-bold mb-4">{{ __('Reports and Analytics') }}</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> {{ __('Export') }} {{ __('Projects') }}\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> {{ __('Export') }} {{ __('Phases') }}\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> {{ __('Export') }} {{ __('Inspections') }}\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">{{ __('Time range:') }}</span>
<select wire:model="dateRange" class="border border-gray-300 rounded px-3 py-1">
<option value="week">{{ __('This week') }}</option>
<option value="month" selected>{{ __('This month') }}</option>
<option value="quarter">{{ __('This quarter') }}</option>
<option value="year">{{ __('This year') }}</option>
</select>
</div>
<button wire:click="loadChartData"
class="btn btn-primary btn-sm">
{{ __('Update') }}
</button>
</div>
</div>
@if(isset($chartData['months']))
<div class="grid gap-6 mb-8">
{{-- Project progress chart --}}
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-semibold mb-4">{{ __('Project Progress (last 6 months)') }}</h3>
<div style="position: relative; height: 300px;">
<canvas id="projectProgressChart"></canvas>
</div>
</div>
{{-- Inspections by type chart --}}
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-semibold mb-4">{{ __('Inspections by Type') }}</h3>
<div style="position: relative; height: 300px;">
<canvas id="inspectionTypesChart"></canvas>
</div>
</div>
{{-- Projects by status chart --}}
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-semibold mb-4">{{ __('Projects by Status') }}</h3>
<div style="position: relative; height: 300px;">
<canvas id="projectsByStatusChart"></canvas>
</div>
</div>
{{-- Average progress by project chart --}}
<div class="bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-semibold mb-4">{{ __('Average Progress by Project') }}</h3>
<div style="position: relative; height: 300px;">
<canvas id="projectPhaseProgressChart"></canvas>
</div>
</div>
</div>
{{-- Key metrics cards --}}
<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 Active Projects') }}
</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">
{{ __('Inspections This Month') }}
</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">
{{ __('Average Progress') }}
</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">
{{ __('Completed Projects') }}
</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">{{ __('Loading data...') }}</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: '{{ __("Progress") }} (%)'
}
}
}
}
});
}
// Inspection Types Chart (Bar)
const inspectionTypesCtx = document.getElementById('inspectionTypesChart');
if (inspectionTypesCtx) {
new Chart(inspectionTypesCtx, {
type: 'bar',
data: {
labels: @json($chartData['inspectionTypes']['labels'] ?? []),
datasets: [{
label: '{{ __("Inspections") }}',
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: '{{ __("Total") }}'
}
}
}
}
});
}
// 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: '{{ __("Projects by Status") }}',
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: '{{ __("Average Progress") }} (%)',
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: '{{ __("Progress") }} (%)'
}
}
}
}
});
}
}
});
</script>