Files

397 lines
22 KiB
PHP
Raw Permalink Normal View History

<div>
<x-slot name="header">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<a href="{{ route('projects.list') }}" class="btn btn-ghost btn-sm px-2">
<x-heroicon-o-arrow-left class="w-4 h-4" />
</a>
<div>
<h2 class="font-bold text-xl leading-tight">{{ $project->name }}</h2>
@if($project->description)
<p class="text-sm text-gray-500 leading-tight mt-0.5">{{ Str::limit($project->description, 80) }}</p>
@endif
</div>
@php
$statusCfg = match($project->status) {
'in_progress' => ['badge-primary', 'En progreso'],
'completed' => ['badge-success', 'Completado'],
'paused' => ['badge-warning', 'Pausado'],
'planning' => ['badge-ghost', 'Planificación'],
default => ['badge-ghost', ucfirst(str_replace('_',' ',$project->status))],
};
@endphp
<span class="badge {{ $statusCfg[0] }} badge-sm">{{ $statusCfg[1] }}</span>
</div>
<div class="flex gap-2">
<a href="{{ route('projects.map', $project) }}" class="btn btn-outline btn-sm gap-1">
<x-heroicon-o-map class="w-4 h-4" />
Mapa
</a>
<a href="{{ route('projects.gantt', $project) }}" class="btn btn-outline btn-sm gap-1">
<x-heroicon-o-calendar-days class="w-4 h-4" />
Gantt
</a>
<a href="{{ route('projects.issues', $project) }}" class="btn btn-outline btn-sm gap-1">
<x-heroicon-o-exclamation-triangle class="w-4 h-4" />
Issues
</a>
</div>
</div>
</x-slot>
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
{{-- ── KPIs fila 1 ────────────────────────────────────────────────────── --}}
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
{{-- Avance global --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Avance global</p>
<p class="mt-1 text-3xl font-bold text-green-600">{{ $stats['global_progress'] }}%</p>
<div class="mt-2 w-full bg-gray-200 rounded-full h-1.5">
<div class="bg-green-500 h-1.5 rounded-full transition-all" style="width: {{ $stats['global_progress'] }}%"></div>
</div>
</div>
<div class="p-3 bg-green-100 rounded-full ml-3 shrink-0">
<x-heroicon-o-chart-bar class="w-5 h-5 text-green-600" />
</div>
</div>
</div>
</div>
{{-- Fases --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Fases</p>
<p class="mt-1 text-3xl font-bold text-blue-600">{{ $stats['total_phases'] }}</p>
@if($stats['delayed_phases'] > 0)
<p class="text-xs text-red-500 mt-0.5">{{ $stats['delayed_phases'] }} con retraso</p>
@else
<p class="text-xs text-gray-400 mt-0.5">Sin retrasos</p>
@endif
</div>
<div class="p-3 {{ $stats['delayed_phases'] > 0 ? 'bg-red-100' : 'bg-blue-100' }} rounded-full">
<x-heroicon-o-rectangle-stack class="w-5 h-5 {{ $stats['delayed_phases'] > 0 ? 'text-red-500' : 'text-blue-600' }}" />
</div>
</div>
</div>
</div>
{{-- Elementos --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Elementos</p>
<p class="mt-1 text-3xl font-bold text-indigo-600">{{ $stats['total_features'] }}</p>
<p class="text-xs text-gray-400 mt-0.5">
{{ $stats['completed_features'] }} completados
· {{ $stats['verified_features'] }} verificados
</p>
</div>
<div class="p-3 bg-indigo-100 rounded-full">
<x-heroicon-o-map-pin class="w-5 h-5 text-indigo-600" />
</div>
</div>
</div>
</div>
{{-- Issues --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Issues abiertos</p>
<p class="mt-1 text-3xl font-bold {{ $stats['open_issues'] > 0 ? 'text-orange-600' : 'text-gray-400' }}">
{{ $stats['open_issues'] }}
</p>
@if($stats['critical_issues'] > 0)
<p class="text-xs text-red-600 font-semibold mt-0.5">{{ $stats['critical_issues'] }} críticos</p>
@else
<p class="text-xs text-gray-400 mt-0.5">0 críticos</p>
@endif
</div>
<div class="p-3 {{ $stats['open_issues'] > 0 ? 'bg-orange-100' : 'bg-gray-100' }} rounded-full">
<x-heroicon-o-exclamation-triangle class="w-5 h-5 {{ $stats['open_issues'] > 0 ? 'text-orange-600' : 'text-gray-400' }}" />
</div>
</div>
</div>
</div>
</div>
{{-- ── KPIs fila 2: Inspecciones ────────────────────────────────────────── --}}
<div class="grid grid-cols-3 gap-4">
<div class="card bg-base-100 shadow">
<div class="card-body p-4 flex-row items-center gap-4">
<div class="p-3 bg-gray-100 rounded-full shrink-0">
<x-heroicon-o-clipboard-document-list class="w-5 h-5 text-gray-600" />
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider">Total inspecciones</p>
<p class="text-2xl font-bold">{{ $stats['total_inspections'] }}</p>
</div>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body p-4 flex-row items-center gap-4">
<div class="p-3 bg-green-100 rounded-full shrink-0">
<x-heroicon-o-check-circle class="w-5 h-5 text-green-600" />
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider">Aprobadas</p>
<p class="text-2xl font-bold text-green-600">{{ $stats['passed_inspections'] }}</p>
</div>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body p-4 flex-row items-center gap-4">
<div class="p-3 {{ $stats['failed_inspections'] > 0 ? 'bg-red-100' : 'bg-gray-100' }} rounded-full shrink-0">
<x-heroicon-o-x-circle class="w-5 h-5 {{ $stats['failed_inspections'] > 0 ? 'text-red-600' : 'text-gray-400' }}" />
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider">Rechazadas</p>
<p class="text-2xl font-bold {{ $stats['failed_inspections'] > 0 ? 'text-red-600' : 'text-gray-400' }}">
{{ $stats['failed_inspections'] }}
</p>
</div>
</div>
</div>
</div>
{{-- ── Main grid: fases + actividad reciente ───────────────────────────── --}}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- LEFT 2/3: Fases con progreso --}}
<div class="lg:col-span-2 space-y-4">
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="font-semibold text-base flex items-center gap-2">
<x-heroicon-o-rectangle-stack class="w-4 h-4" />
Fases del proyecto
</h3>
<a href="{{ route('projects.gantt', $project) }}" class="btn btn-xs btn-outline gap-1">
<x-heroicon-o-calendar-days class="w-3.5 h-3.5" />
Gantt
</a>
</div>
@if($phases->isEmpty())
<p class="text-sm text-gray-400 text-center py-6">Sin fases aún.</p>
@else
<div class="space-y-3">
@foreach($phases as $phase)
@php
$pct = round($phase->progress_percent ?? 0);
$isDelayed = $phase->planned_end && $phase->planned_end < now() && $pct < 100;
$barColor = $isDelayed ? 'bg-red-500' : ($pct >= 100 ? 'bg-green-500' : 'bg-blue-500');
$featureCount = $phase->layers->sum('features_count');
@endphp
<div class="border border-base-200 rounded-lg p-3 hover:border-primary/40 transition-colors">
<div class="flex items-start justify-between gap-2 mb-2">
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm truncate">{{ $phase->name }}</p>
<div class="flex flex-wrap items-center gap-2 text-xs text-gray-500 mt-0.5">
<span>{{ $phase->layers_count }} capa(s)</span>
<span>·</span>
<span>{{ $featureCount }} elementos</span>
@if($phase->planned_start && $phase->planned_end)
<span>·</span>
<span>{{ $phase->planned_start->format('d/m/y') }} {{ $phase->planned_end->format('d/m/y') }}</span>
@endif
</div>
</div>
<div class="flex items-center gap-2 shrink-0">
@if($isDelayed)
<span class="badge badge-error badge-xs">Retraso</span>
@elseif($pct >= 100)
<span class="badge badge-success badge-xs">Completada</span>
@endif
<span class="text-sm font-bold {{ $isDelayed ? 'text-red-600' : ($pct >= 100 ? 'text-green-600' : 'text-blue-600') }}">
{{ $pct }}%
</span>
</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="{{ $barColor }} h-2 rounded-full transition-all" style="width: {{ $pct }}%"></div>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
{{-- Empresas participantes --}}
@if($companies->isNotEmpty())
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<h3 class="font-semibold text-base flex items-center gap-2 mb-3">
<x-heroicon-o-building-office-2 class="w-4 h-4" />
Empresas participantes
</h3>
<div class="flex flex-wrap gap-3">
@foreach($companies as $company)
<div class="flex items-center gap-2 border border-base-200 rounded-lg px-3 py-2">
@if($company->logo_path && \Illuminate\Support\Facades\Storage::disk('public')->exists($company->logo_path))
<img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->url($company->logo_path) }}"
alt="" class="w-7 h-7 object-contain rounded" />
@else
<x-heroicon-o-building-office class="w-5 h-5 opacity-40" />
@endif
<div>
<p class="text-xs font-semibold leading-tight">{{ $company->apodo ?: $company->name }}</p>
@if($company->pivot->role_in_project)
<p class="text-xs text-gray-400">{{ $company->pivot->role_in_project }}</p>
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
</div>
{{-- RIGHT 1/3: Actividad reciente --}}
<div class="space-y-5">
{{-- Equipo --}}
@if($teamMembers->isNotEmpty())
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<h3 class="font-semibold text-base flex items-center gap-2 mb-3">
<x-heroicon-o-users class="w-4 h-4" />
Equipo ({{ $teamMembers->count() }})
</h3>
<div class="space-y-2">
@foreach($teamMembers->take(8) as $member)
<div class="flex items-center gap-2">
<div class="avatar placeholder shrink-0">
<div class="bg-neutral text-neutral-content rounded-full w-7">
<span class="text-xs">{{ strtoupper(substr($member->name, 0, 1)) }}</span>
</div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium truncate">{{ $member->name }}</p>
@if($member->pivot->role_in_project)
<p class="text-xs text-gray-400 truncate">{{ $member->pivot->role_in_project }}</p>
@endif
</div>
@foreach($member->roles->take(1) as $role)
<span class="badge badge-xs {{ $role->name === 'Admin' ? 'badge-error' : 'badge-ghost' }}">{{ $role->name }}</span>
@endforeach
</div>
@endforeach
</div>
</div>
</div>
@endif
{{-- Issues recientes --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-base flex items-center gap-2">
<x-heroicon-o-exclamation-triangle class="w-4 h-4 text-orange-500" />
Issues abiertos
</h3>
<a href="{{ route('projects.issues', $project) }}" class="btn btn-xs btn-outline">Ver todos</a>
</div>
@if($recentIssues->isEmpty())
<div class="text-center py-4 text-gray-400">
<x-heroicon-o-check-circle class="w-7 h-7 mx-auto mb-1 opacity-25" />
<p class="text-xs">Sin issues abiertos</p>
</div>
@else
<div class="space-y-2">
@foreach($recentIssues as $issue)
@php
$pCfg = match($issue->priority ?? 'medium') {
'critical' => 'badge-error',
'high' => 'badge-warning',
'medium' => 'badge-info',
default => 'badge-ghost',
};
@endphp
<div class="p-2.5 rounded-lg bg-base-200">
<div class="flex items-start gap-1.5">
<span class="badge badge-xs {{ $pCfg }} shrink-0 mt-0.5">{{ ucfirst($issue->priority ?? 'medium') }}</span>
<p class="text-xs font-medium truncate">{{ $issue->title }}</p>
</div>
@if($issue->feature)
<p class="text-xs text-gray-400 mt-0.5 truncate">
<x-heroicon-o-map-pin class="w-2.5 h-2.5 inline" /> {{ $issue->feature->name }}
</p>
@endif
</div>
@endforeach
</div>
@endif
</div>
</div>
{{-- Inspecciones recientes --}}
<div class="card bg-base-100 shadow">
<div class="card-body p-5">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-base flex items-center gap-2">
<x-heroicon-o-clipboard-document-list class="w-4 h-4 text-yellow-500" />
Inspecciones recientes
</h3>
<a href="{{ route('projects.map', $project) }}?tab=inspections" class="btn btn-xs btn-outline">Ver en mapa</a>
</div>
@if($recentInspections->isEmpty())
<div class="text-center py-4 text-gray-400">
<x-heroicon-o-clipboard-document-list class="w-7 h-7 mx-auto mb-1 opacity-25" />
<p class="text-xs">Sin inspecciones</p>
</div>
@else
<div class="space-y-2">
@foreach($recentInspections as $ins)
@php
$iCfg = match($ins->result ?? '') {
'pass' => ['badge-success', 'OK'],
'fail' => ['badge-error', 'Fallo'],
default => ['badge-ghost', 'Pendiente'],
};
@endphp
<div class="p-2.5 rounded-lg bg-base-200">
<div class="flex items-start justify-between gap-1">
<p class="text-xs font-medium truncate flex-1">
{{ $ins->template?->name ?? 'Inspección' }}
</p>
<span class="badge badge-xs {{ $iCfg[0] }} shrink-0">{{ $iCfg[1] }}</span>
</div>
<div class="flex items-center justify-between mt-0.5">
@if($ins->feature)
<p class="text-xs text-gray-400 truncate">
<x-heroicon-o-map-pin class="w-2.5 h-2.5 inline" /> {{ $ins->feature->name }}
</p>
@endif
<p class="text-xs text-gray-400 shrink-0 ml-1">{{ $ins->created_at->diffForHumans() }}</p>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
{{-- end right --}}
</div>
{{-- end main grid --}}
</div>
</div>
</div>{{-- end root --}}