8025fa6d05
Permissions now actually govern access instead of the hard-coded Admin role:
- Super-admin bypass (see all projects / full access) -> can('manage all')
in Project::scopeAccessibleBy, ProjectMap, ProjectDashboard, PhaseGantt,
LayerManager, ProjectReportController.
- Redundant '|| hasRole(Admin)' fallbacks dropped (Gate::before already lets
manage-all through can()): LayerManager (upload/delete layers), MediaManager
(upload), ProjectMap (update progress), ProjectUsers/ProjectCompanies
(assign users).
- Admin-only screens now gated by the matching permission: AdminUsers/UserView
-> can('view users'), UserForm -> can('create users')|can('edit users'),
CompanyView -> can('view companies').
- MediaManager delete: can('delete media') OR owner.
- Kept UserForm's domain guard (can't remove your own Admin role).
Note: the /admin route group still has middleware can:manage all, so admin
screens stay super-admin-only until that group is relaxed per-route.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
108 lines
3.8 KiB
PHP
108 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use Livewire\Component;
|
|
use Livewire\Attributes\Layout;
|
|
use App\Models\Project;
|
|
use App\Models\Phase;
|
|
use App\Models\Feature;
|
|
use App\Models\Inspection;
|
|
use App\Models\Issue;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
#[Layout('layouts.app')]
|
|
class ProjectDashboard extends Component
|
|
{
|
|
public Project $project;
|
|
|
|
// Computed stats (cached as properties after mount)
|
|
public array $stats = [];
|
|
public $phases;
|
|
public $recentInspections;
|
|
public $recentIssues;
|
|
public $teamMembers;
|
|
public $companies;
|
|
|
|
public function mount(Project $project): void
|
|
{
|
|
$this->project = $project;
|
|
$this->checkAccess();
|
|
$this->loadData();
|
|
}
|
|
|
|
private function checkAccess(): void
|
|
{
|
|
$user = Auth::user();
|
|
if ($user->can('manage all')) return;
|
|
if (!$this->project->users()->where('user_id', $user->id)->exists()) abort(403);
|
|
}
|
|
|
|
private function loadData(): void
|
|
{
|
|
$pid = $this->project->id;
|
|
|
|
$this->phases = Phase::where('project_id', $pid)
|
|
->withCount('layers')
|
|
->with(['layers' => fn($q) => $q->withCount('features')])
|
|
->orderBy('order')
|
|
->get();
|
|
|
|
$totalFeatures = Feature::whereHas('layer.phase', fn($q) => $q->where('project_id', $pid))->count();
|
|
$completedFeatures = Feature::whereHas('layer.phase', fn($q) => $q->where('project_id', $pid))
|
|
->where('status', 'completed')->count();
|
|
$verifiedFeatures = Feature::whereHas('layer.phase', fn($q) => $q->where('project_id', $pid))
|
|
->where('status', 'verified')->count();
|
|
|
|
$openIssues = Issue::where('project_id', $pid)->where('status', 'open')->count();
|
|
$closedIssues = Issue::where('project_id', $pid)->where('status', 'closed')->count();
|
|
$criticalIssues = Issue::where('project_id', $pid)->where('status', 'open')->where('priority', 'critical')->count();
|
|
|
|
$totalInspections = Inspection::where('project_id', $pid)->count();
|
|
$passedInspections = Inspection::where('project_id', $pid)->where('result', 'pass')->count();
|
|
$failedInspections = Inspection::where('project_id', $pid)->where('result', 'fail')->count();
|
|
|
|
$globalProgress = $this->phases->avg('progress_percent') ?? 0;
|
|
|
|
$delayedPhases = $this->phases->filter(fn($p) =>
|
|
$p->planned_end && $p->planned_end < now() && $p->progress_percent < 100
|
|
)->count();
|
|
|
|
$this->stats = [
|
|
'global_progress' => round($globalProgress),
|
|
'total_phases' => $this->phases->count(),
|
|
'delayed_phases' => $delayedPhases,
|
|
'total_features' => $totalFeatures,
|
|
'completed_features' => $completedFeatures,
|
|
'verified_features' => $verifiedFeatures,
|
|
'open_issues' => $openIssues,
|
|
'closed_issues' => $closedIssues,
|
|
'critical_issues' => $criticalIssues,
|
|
'total_inspections' => $totalInspections,
|
|
'passed_inspections' => $passedInspections,
|
|
'failed_inspections' => $failedInspections,
|
|
];
|
|
|
|
$this->recentInspections = Inspection::where('project_id', $pid)
|
|
->with(['feature', 'template', 'user'])
|
|
->latest()->take(6)->get();
|
|
|
|
$this->recentIssues = Issue::where('project_id', $pid)
|
|
->with(['feature', 'reporter'])
|
|
->where('status', '!=', 'closed')
|
|
->orderByRaw("FIELD(priority,'critical','high','medium','low')")
|
|
->take(6)->get();
|
|
|
|
$this->teamMembers = $this->project->users()->with('roles')->get();
|
|
|
|
$this->companies = $this->project->companies()->get();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.projects.project-dashboard', [
|
|
'project' => $this->project,
|
|
]);
|
|
}
|
|
}
|