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>
This commit is contained in:
2026-06-16 18:05:53 +02:00
parent 052e1397df
commit 7d854ffb0a
85 changed files with 8499 additions and 1339 deletions
+70 -29
View File
@@ -7,6 +7,8 @@ use App\Http\Controllers\OfflineSyncController;
use App\Livewire\ProjectMap;
use App\Livewire\ProjectList;
use App\Livewire\PhaseProgress;
use App\Livewire\PhaseGantt;
use App\Http\Controllers\ProjectReportController;
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\ConfirmablePasswordController;
@@ -36,39 +38,61 @@ Route::middleware(['auth'])->group(function () {
// Dashboard principal (vista con estadísticas y lista de proyectos)
Route::get('/dashboard', function () {
$user = \Illuminate\Support\Facades\Auth::user();
$projectIds = \App\Models\Project::accessibleBy($user)->pluck('id');
$projects = \App\Models\Project::accessibleBy($user)
->withCount('phases')
->with('phases')
->latest()
->take(5)
->get();
->with(['phases' => fn($q) => $q->orderBy('order')])
->latest()->take(6)->get();
$allProjects = \App\Models\Project::accessibleBy($user);
$activeProjects = (clone $allProjects)->where('status', 'in_progress');
$totalPhases = \App\Models\Phase::whereIn('project_id', (clone $allProjects)->pluck('id'))->count();
$totalFeatures = \App\Models\Feature::whereIn('layer_id', function($q) use ($allProjects) {
$q->select('id')->from('layers')->whereIn('project_id', (clone $allProjects)->pluck('id'));
})->count();
$activeProjects = \App\Models\Project::accessibleBy($user)->where('status', 'in_progress')->count();
$totalProjects = \App\Models\Project::accessibleBy($user)->count();
$totalPhases = \App\Models\Phase::whereIn('project_id', $projectIds)->count();
$totalFeatures = \App\Models\Feature::whereHas('layer.phase', fn($q) => $q->whereIn('project_id', $projectIds))->count();
$globalProgress = \App\Models\Phase::whereIn('project_id', $projectIds)->avg('progress_percent') ?? 0;
$globalProgress = \App\Models\Phase::whereIn('project_id', (clone $allProjects)->pluck('id'))->avg('progress_percent') ?? 0;
$openIssues = \App\Models\Issue::whereIn('project_id', $projectIds)->where('status', 'open')->count();
$criticalIssues = \App\Models\Issue::whereIn('project_id', $projectIds)->where('status', 'open')->where('priority', 'critical')->count();
$inspections = \App\Models\Inspection::whereIn('project_id', (clone $allProjects)->pluck('id'))
->with(['template', 'feature'])
->latest()
->take(5)
->get();
$pendingInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'pending')->count();
$completedInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'completed')->count();
$rejectedInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)->where('status', 'rejected')->count();
$recentInspections = \App\Models\Inspection::whereIn('project_id', $projectIds)
->with(['template', 'feature', 'project'])
->latest()->take(5)->get();
$recentIssues = \App\Models\Issue::whereIn('project_id', $projectIds)
->with(['feature', 'reporter', 'project'])
->where('status', '!=', 'closed')
->orderByRaw("FIELD(priority,'critical','high','medium','low')")
->take(5)->get();
// Projects with delay (planned_end exceeded and not completed)
$delayedPhases = \App\Models\Phase::whereIn('project_id', $projectIds)
->whereNotNull('planned_end')
->where('planned_end', '<', now())
->where('progress_percent', '<', 100)
->with('project')
->count();
return view('dashboard', [
'stats' => [
'active_projects' => $activeProjects->count(),
'total_projects' => $allProjects->count(),
'total_phases' => $totalPhases,
'total_features' => $totalFeatures,
'global_progress' => round($globalProgress),
'active_projects' => $activeProjects,
'total_projects' => $totalProjects,
'total_phases' => $totalPhases,
'total_features' => $totalFeatures,
'global_progress' => round($globalProgress),
'open_issues' => $openIssues,
'critical_issues' => $criticalIssues,
'pending_inspections' => $pendingInspections,
'completed_inspections'=> $completedInspections,
'rejected_inspections' => $rejectedInspections,
'delayed_phases' => $delayedPhases,
],
'recentProjects' => $projects,
'recentInspections' => $inspections,
'recentProjects' => $projects,
'recentInspections' => $recentInspections,
'recentIssues' => $recentIssues,
]);
})->name('dashboard');
Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboard');
@@ -79,9 +103,12 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
});
// ------------------------------------------------------------
// Gestión de proyectos (CRUD completo)
// Gestión de proyectos
// ------------------------------------------------------------
Route::resource('projects', ProjectController::class);
// Create/Edit handled by unified Livewire component
Route::get('/projects/create', \App\Livewire\ProjectForm::class)->name('projects.create');
Route::get('/projects/{project}/edit', \App\Livewire\ProjectForm::class)->name('projects.edit');
Route::resource('projects', ProjectController::class)->except(['create', 'edit']);
// Ruta personalizada para ver el mapa de un proyecto específico
Route::get('/projects/{project}/map', [ProjectController::class, 'map'])->name('projects.map');
// Ruta para que el componente Livewire muestre/gestione el progreso de una fase
@@ -95,6 +122,16 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
// Rutas para el LayerManager:
Route::get('/projects/{project}/phases/{phase}/layers/manage', \App\Livewire\LayerManager::class)->name('layers.manage');
// Cronograma Gantt y reporte del proyecto
Route::get('/projects/{project}/gantt', PhaseGantt::class)->name('projects.gantt');
Route::get('/projects/{project}/report', [ProjectReportController::class, 'show'])->name('projects.report');
// Issues del proyecto
Route::get('/projects/{project}/issues', \App\Livewire\IssueManager::class)->name('projects.issues');
// Dashboard por proyecto
Route::get('/projects/{project}/dashboard', \App\Livewire\ProjectDashboard::class)->name('projects.dashboard');
// Cliente: portal cliente
Route::middleware(['auth', 'role:client'])->prefix('client')->name('client.')->group(function () {
Route::get('/', function () {
@@ -104,16 +141,20 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa
// Admin: gestión de usuarios y roles
Route::middleware(['can:manage all'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/users', function () {
return view('admin.users');
})->name('users');
Route::get('/users', function () { return view('admin.users'); })->name('users');
Route::get('/users/create', \App\Livewire\UserForm::class)->name('users.create');
Route::get('/users/{user}', \App\Livewire\UserView::class)->name('users.show');
Route::get('/users/{user}/edit', \App\Livewire\UserForm::class)->name('users.edit');
});
// Gestor de medios
Route::get('/projects/{project}/media', function (\App\Models\Project $project) {
return view('projects.media', compact('project'));
})->name('projects.media');
Route::get('/companies', \App\Livewire\CompanyManagement::class)->name('companies.manage');
Route::get('/companies', \App\Livewire\CompanyManagement::class)->name('companies.manage');
Route::get('/companies/create', \App\Livewire\CompanyForm::class)->name('companies.create');
Route::get('/companies/{company}', \App\Livewire\CompanyView::class)->name('companies.show');
Route::get('/companies/{company}/edit', \App\Livewire\CompanyForm::class)->name('companies.edit');
// ------------------------------------------------------------
// Sincronización offline (para trabajadores en campo)