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:
@@ -0,0 +1,275 @@
|
||||
<div class="p-4 space-y-4">
|
||||
|
||||
{{-- Page header --}}
|
||||
<div class="flex items-center justify-between flex-wrap gap-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('projects.map', $project) }}"
|
||||
class="btn btn-sm btn-ghost gap-1">
|
||||
<x-heroicon-o-arrow-left class="w-4 h-4" />
|
||||
{{ __('Back to Map') }}
|
||||
</a>
|
||||
<h1 class="text-xl font-bold">{{ __('Cronograma') }}: {{ $project->name }}</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('projects.report', $project) }}"
|
||||
target="_blank"
|
||||
class="btn btn-sm btn-outline gap-1">
|
||||
<x-heroicon-o-document-text class="w-4 h-4" />
|
||||
{{ __('Report PDF') }}
|
||||
</a>
|
||||
<span class="text-sm text-base-content/60">
|
||||
{{ $project->start_date?->format('d/m/Y') ?? __('N/A') }}
|
||||
—
|
||||
{{ $project->end_date_estimated?->format('d/m/Y') ?? __('N/A') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Legend --}}
|
||||
<div class="flex items-center gap-4 text-sm flex-wrap">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="inline-block w-5 h-3 rounded" style="background:#3b82f6"></span>
|
||||
{{ __('Planificado') }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="inline-block w-5 h-3 rounded" style="background:#22c55e"></span>
|
||||
{{ __('Real') }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="inline-block w-5 h-3 rounded border-2" style="background:#fee2e2;border-color:#ef4444"></span>
|
||||
{{ __('Retrasado') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Editor de fechas por fase (siempre visible) --}}
|
||||
<div class="bg-base-100 rounded-box border border-base-300 p-4 mb-4">
|
||||
<h3 class="font-semibold text-sm mb-3">Fechas planificadas y reales por fase</h3>
|
||||
<div class="space-y-3">
|
||||
@foreach($phases as $phase)
|
||||
<div x-data="{
|
||||
ps: '{{ $phase->planned_start?->format('Y-m-d') ?? '' }}',
|
||||
pe: '{{ $phase->planned_end?->format('Y-m-d') ?? '' }}',
|
||||
as_: '{{ $phase->actual_start?->format('Y-m-d') ?? '' }}',
|
||||
ae: '{{ $phase->actual_end?->format('Y-m-d') ?? '' }}'
|
||||
}" class="grid grid-cols-2 md:grid-cols-5 gap-2 items-center text-sm border-b pb-3 last:border-0">
|
||||
<div class="font-medium truncate flex items-center gap-1">
|
||||
<span class="w-2 h-2 rounded-full inline-block flex-shrink-0" style="background:{{ $phase->color ?? '#3b82f6' }}"></span>
|
||||
{{ $phase->name }}
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label-text text-xs text-gray-500">Plan. inicio</label>
|
||||
<input type="date" x-model="ps" class="input input-xs input-bordered" />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label-text text-xs text-gray-500">Plan. fin</label>
|
||||
<input type="date" x-model="pe" class="input input-xs input-bordered" />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label-text text-xs text-gray-500">Real inicio</label>
|
||||
<input type="date" x-model="as_" class="input input-xs input-bordered" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="label-text text-xs text-gray-500">Real fin</label>
|
||||
<div class="flex gap-1">
|
||||
<input type="date" x-model="ae" class="input input-xs input-bordered flex-1" />
|
||||
<button @click="$wire.updatePhaseDates({{ $phase->id }}, ps, pe, as_, ae)"
|
||||
class="btn btn-xs btn-primary">
|
||||
✓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(empty($ganttData))
|
||||
<div class="alert alert-info">
|
||||
<x-heroicon-o-information-circle class="w-5 h-5" />
|
||||
<span>Define fechas planificadas arriba para ver el diagrama.</span>
|
||||
</div>
|
||||
@else
|
||||
{{-- Gantt table --}}
|
||||
<div class="bg-base-100 rounded-box border border-base-300 overflow-x-auto">
|
||||
<table class="w-full text-sm" style="min-width:900px;">
|
||||
<thead>
|
||||
<tr class="border-b border-base-300">
|
||||
{{-- Phase name column --}}
|
||||
<th class="text-left px-3 py-2 font-semibold bg-base-200" style="width:200px;min-width:200px;">
|
||||
{{ __('Fase') }}
|
||||
</th>
|
||||
|
||||
{{-- Month header row --}}
|
||||
<th class="px-0 py-0 bg-base-200" style="min-width:400px;">
|
||||
@php
|
||||
$projectStart = $project->start_date ?? now()->startOfMonth();
|
||||
$projectEnd = $project->end_date_estimated ?? now()->addMonths(6);
|
||||
$totalDays = max(1, $projectStart->diffInDays($projectEnd));
|
||||
|
||||
// Build month segments
|
||||
$months = [];
|
||||
$cursor = $projectStart->copy()->startOfMonth();
|
||||
while ($cursor->lte($projectEnd)) {
|
||||
$mStart = $cursor->copy()->max($projectStart);
|
||||
$mEnd = $cursor->copy()->endOfMonth()->min($projectEnd);
|
||||
$days = max(1, $mStart->diffInDays($mEnd) + 1);
|
||||
$widthPct = round(($days / $totalDays) * 100, 2);
|
||||
$months[] = [
|
||||
'label' => $cursor->translatedFormat('M Y'),
|
||||
'width_pct' => $widthPct,
|
||||
];
|
||||
$cursor->addMonthNoOverflow();
|
||||
}
|
||||
@endphp
|
||||
<div class="flex w-full border-b border-base-300">
|
||||
@foreach($months as $month)
|
||||
<div class="text-center text-xs py-1 font-medium border-r border-base-300 last:border-r-0 truncate"
|
||||
style="width:{{ $month['width_pct'] }}%;flex-shrink:0;">
|
||||
{{ $month['label'] }}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</th>
|
||||
|
||||
{{-- Dates column --}}
|
||||
<th class="text-left px-3 py-2 font-semibold bg-base-200 whitespace-nowrap" style="width:160px;min-width:160px;">
|
||||
{{ __('Fechas') }}
|
||||
</th>
|
||||
|
||||
{{-- Status column --}}
|
||||
<th class="text-center px-3 py-2 font-semibold bg-base-200" style="width:110px;min-width:110px;">
|
||||
{{ __('Estado') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($ganttData as $phase)
|
||||
<tr class="border-b border-base-300 hover:bg-base-50 transition-colors {{ $phase['is_delayed'] ? 'bg-red-50' : '' }}">
|
||||
|
||||
{{-- Phase name --}}
|
||||
<td class="px-3 py-3" style="width:200px;min-width:200px;vertical-align:middle;">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-block w-3 h-3 rounded-full flex-shrink-0"
|
||||
style="background:{{ $phase['color'] }}"></span>
|
||||
<span class="font-medium truncate" title="{{ $phase['name'] }}">
|
||||
{{ $phase['name'] }}
|
||||
</span>
|
||||
</div>
|
||||
@if($phase['features_count'] > 0)
|
||||
<div class="ml-5 text-xs text-base-content/50 mt-0.5">
|
||||
{{ $phase['features_count'] }} {{ __('elementos') }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Gantt bar cell --}}
|
||||
<td class="px-0 py-3" style="vertical-align:middle;">
|
||||
<div class="relative w-full" style="height:36px;">
|
||||
|
||||
{{-- Month grid lines --}}
|
||||
@php $offset = 0; @endphp
|
||||
@foreach($months as $i => $month)
|
||||
@if($i > 0)
|
||||
<div class="absolute top-0 bottom-0 border-l border-base-300/50"
|
||||
style="left:{{ $offset }}%;"></div>
|
||||
@endif
|
||||
@php $offset += $month['width_pct']; @endphp
|
||||
@endforeach
|
||||
|
||||
{{-- Planned bar --}}
|
||||
<div class="absolute rounded"
|
||||
style="
|
||||
top: 4px;
|
||||
height: 13px;
|
||||
left: {{ $phase['p_start_pct'] }}%;
|
||||
width: {{ max(0.5, $phase['p_width_pct']) }}%;
|
||||
background: {{ $phase['is_delayed'] ? '#fca5a5' : $phase['color'] }};
|
||||
border: {{ $phase['is_delayed'] ? '2px solid #ef4444' : 'none' }};
|
||||
opacity: 0.85;
|
||||
"
|
||||
title="{{ __('Planificado') }}: {{ $phase['planned_start'] }} - {{ $phase['planned_end'] }}">
|
||||
</div>
|
||||
|
||||
{{-- Actual bar (if exists) --}}
|
||||
@if($phase['a_start_pct'] !== null && $phase['a_width_pct'] !== null)
|
||||
<div class="absolute rounded"
|
||||
style="
|
||||
top: 19px;
|
||||
height: 13px;
|
||||
left: {{ $phase['a_start_pct'] }}%;
|
||||
width: {{ max(0.5, $phase['a_width_pct']) }}%;
|
||||
background: #22c55e;
|
||||
opacity: 0.85;
|
||||
"
|
||||
title="{{ __('Real') }}: {{ $phase['actual_start'] }} - {{ $phase['actual_end'] ?? __('En curso') }}">
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Progress label --}}
|
||||
<div class="absolute inset-0 flex items-center"
|
||||
style="left: {{ $phase['p_start_pct'] }}%; width: {{ max(0.5, $phase['p_width_pct']) }}%;">
|
||||
<span class="text-xs font-bold text-white drop-shadow px-1 truncate"
|
||||
style="font-size:10px; line-height:13px; position:absolute; top:4px; left:2px;">
|
||||
{{ $phase['progress'] }}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Dates column --}}
|
||||
<td class="px-3 py-3 text-xs" style="width:160px;min-width:160px;vertical-align:middle;">
|
||||
<div class="space-y-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-block w-2 h-2 rounded-full flex-shrink-0" style="background:{{ $phase['color'] }}"></span>
|
||||
<span class="text-base-content/70">{{ $phase['planned_start'] }} – {{ $phase['planned_end'] }}</span>
|
||||
</div>
|
||||
@if($phase['actual_start'])
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-block w-2 h-2 rounded-full flex-shrink-0" style="background:#22c55e"></span>
|
||||
<span class="text-base-content/70">
|
||||
{{ $phase['actual_start'] }} – {{ $phase['actual_end'] ?? __('En curso') }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Status badge --}}
|
||||
<td class="px-3 py-3 text-center" style="width:110px;min-width:110px;vertical-align:middle;">
|
||||
@if($phase['is_delayed'])
|
||||
<span class="badge badge-error badge-sm gap-1">
|
||||
<x-heroicon-o-exclamation-triangle class="w-3 h-3" />
|
||||
{{ __('En retraso') }}
|
||||
</span>
|
||||
@elseif($phase['progress'] >= 100)
|
||||
<span class="badge badge-success badge-sm gap-1">
|
||||
<x-heroicon-o-check-circle class="w-3 h-3" />
|
||||
{{ __('Completado') }}
|
||||
</span>
|
||||
@elseif($phase['progress'] > 0)
|
||||
<span class="badge badge-info badge-sm">
|
||||
{{ $phase['progress'] }}%
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-ghost badge-sm">
|
||||
{{ __('Pendiente') }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- Summary footer --}}
|
||||
<div class="text-xs text-base-content/50 text-right">
|
||||
{{ count($ganttData) }} {{ __('fases') }}
|
||||
•
|
||||
{{ __('Actualizado') }}: {{ now()->format('d/m/Y H:i') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user