Files
construprogress/resources/views/livewire/projects/phase-gantt.blade.php
T
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

276 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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') }}
&mdash;
{{ $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') }}
&bull;
{{ __('Actualizado') }}: {{ now()->format('d/m/Y H:i') }}
</div>
@endif
</div>