===== STRUCTURE =====
.
├── CityController.php
├── DashboardController.php
├── DeplacementController.php
├── ListingController.php
├── output.txt
├── PermissionController.php
├── SalaryController.php
├── TicketController.php
├── UserController.php
└── VisitController.php

1 directory, 10 files

===== CONTENU DES FICHIERS =====

----- ./PermissionController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Permission;
use App\Models\User;

class PermissionController extends Controller
{
    public function index(Request $request)
    {
        $query = Permission::with('user')->orderByDesc('start_date');

        if ($search = $request->get('search')) {
            $query->whereHas('user', fn($q)=>$q->where('name','like',"%$search%"));
        }

        if ($statut = $request->get('status')) {
            $query->where('status',$statut);
        }

        if ($reason = $request->get('reason')) {
            $query->where('reason',$reason);
        }

        if ($from = $request->get('from') and $to = $request->get('to')) {
            $query->whereBetween('start_date', [$from,$to]);
        }

        $permissions = $query->paginate(10)->withQueryString();

        return view('pages.permissions.index', compact('permissions'));
    }

    public function show(Permission $permission)
    {
        $permission->load('user');
        return view('pages.permissions.show', compact('permission'));
    }

    public function updateStatus(Request $request, Permission $permission)
    {
        $validated = $request->validate(['status'=>'required|in:accepté,en_cours,refus']);
        $permission->update(['status'=>$validated['status']]);
        return back()->with('success','Statut mis à jour.');
    }
}
----- ./output.txt -----

----- ./DashboardController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Visit;
use App\Models\Deplacement;
use App\Models\Permission;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class DashboardController extends Controller
{
    public function index()
    {
        $now = Carbon::now();

        // =========================
        // KPI PRINCIPAUX
        // =========================
        $totalUsers = User::count();
        $activeUsers = User::where('is_active', true)->count();

        $totalVisits = Visit::count();
        $validVisits = Visit::where('status', 'valide')->count();

        $totalDeplacements = Deplacement::count();
        $deplacementsEnCours = Deplacement::where('status', 'en_cours')->count();

        $totalPermissions = Permission::count();
        $permissionsPending = Permission::where('status', 'en_cours')->count();

        // =========================
        // VISITES PAR STATUT
        // =========================
        $visitesByStatus = Visit::select('status', DB::raw('COUNT(*) as count'))
            ->groupBy('status')
            ->pluck('count', 'status')
            ->toArray();

        // Garantir les clés
        $allStatuses = ['valide', 'invalide', 'double', 'non_realisee'];
        foreach ($allStatuses as $status) {
            if (!isset($visitesByStatus[$status])) {
                $visitesByStatus[$status] = 0;
            }
        }

        // =========================
        // TOP DÉLÉGUÉS
        // =========================
        $topDelegues = User::select('users.id', 'users.name', DB::raw('COUNT(visits.id) as total'))
            ->join('visits', 'users.id', '=', 'visits.user_id')
            ->where('visits.status', 'valide')
            ->groupBy('users.id', 'users.name')
            ->orderByDesc('total')
            ->take(5)
            ->get();

        // =========================
        // ÉVOLUTION SUR 12 MOIS
        // =========================
        $rawVisitsPerMonth = Visit::select(
                DB::raw("YEAR(date_visit) as year"),
                DB::raw("MONTH(date_visit) as month"),
                DB::raw("COUNT(*) as total")
            )
            ->where('date_visit', '>=', $now->copy()->subMonths(11)->startOfMonth())
            ->groupBy('year', 'month')
            ->orderBy('year')
            ->orderBy('month')
            ->get()
            ->keyBy(function ($item) {
                return $item->year . '-' . str_pad($item->month, 2, '0', STR_PAD_LEFT);
            });

        $visitsPerMonth = collect();

        for ($i = 11; $i >= 0; $i--) {
            $date = $now->copy()->subMonths($i);
            $key = $date->format('Y-m');

            $visitsPerMonth->push([
                'month' => $date->translatedFormat('M'),
                'total' => $rawVisitsPerMonth[$key]->total ?? 0,
            ]);
        }

        // =========================
        // ACTIVITÉS RÉCENTES
        // =========================
        $recentVisits = Visit::with('user')
            ->latest('date_visit')
            ->take(5)
            ->get();

        // =========================
        // TAUX
        // =========================
        $validVisitsRate = $totalVisits > 0 ? round(($validVisits / $totalVisits) * 100, 1) : 0;
        $activeUsersRate = $totalUsers > 0 ? round(($activeUsers / $totalUsers) * 100, 1) : 0;

        return view('pages.dashboard.admin-dashboard', compact(
            'totalUsers',
            'activeUsers',
            'totalVisits',
            'validVisits',
            'totalDeplacements',
            'deplacementsEnCours',
            'totalPermissions',
            'permissionsPending',
            'visitesByStatus',
            'topDelegues',
            'visitsPerMonth',
            'recentVisits',
            'validVisitsRate',
            'activeUsersRate'
        ));
    }
}
----- ./ListingController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Listing;
use App\Models\City;
use App\Models\User;
use Illuminate\Http\Request;

class ListingController extends Controller
{
    const TYPES = [
        'PRIVE','PUBLIC','SEMI-PUBLIC','MILITAIRE','PHARMACIE','GROSSISTERIE','MATERNITE','URGENCE',
    ];

    const SPECIALTIES = [
        "Médecine Générale","Cardiologie","Dermatologie-Vénérologie","Endocrinologie-Diabétologie-Nutrition",
        "Gastro-entérologie","Gériatrie","Hématologie","Médecine Interne","Médecine Physique et de Réadaptation",
        "Néphrologie","Neurologie","Oncologie Médicale","Pédiatrie","Pneumologie","Psychiatrie","Rhumatologie",
        "Allergologie","Maladies Infectieuses","Médecine du Sport","Médecine Légale","Chirurgie Générale",
        "Chirurgie Digestive","Chirurgie Cardiaque","Chirurgie Vasculaire","Neurochirurgie",
        "Chirurgie Orthopédique et Traumatologique","Chirurgie Plastique, Reconstructrice et Esthétique",
        "Chirurgie Pédiatrique","Chirurgie Thoracique","Urologie","Ophtalmologie","ORL","Chirurgie Maxillo-Faciale",
        "Stomatologie","Gynécologie-Obstétrique","Anesthésie-Réanimation","Réanimation Médicale",
        "Radiologie et Imagerie Médicale","Médecine Nucléaire","Anatomie Pathologique","Biologie Médicale",
        "Génétique Médicale","Médecine d'Urgence","Santé Publique","Médecine du Travail","Médecine Communautaire",
        "Chirurgie Dentaire","Orthodontie","Parodontologie","Prothèse Dentaire","Odontologie Pédiatrique"
    ];

    /** Liste des listings avec filtres et tri */
    public function index(Request $request)
    {
        $query = Listing::query()->with('city.users');

        if ($request->search) {
            $query->where(function($q) use ($request) {
                $q->where('name', 'like', '%' . $request->search . '%')
                  ->orWhere('address', 'like', '%' . $request->search . '%');
            });
        }

        if ($request->type) $query->where('type', $request->type);
        if ($request->specialty) $query->where('specialty', $request->specialty);
        if ($request->city) $query->where('city_id', $request->city);
        if ($request->delegate) {
            $query->whereHas('city.users', fn($q) => $q->where('name', $request->delegate));
        }

        if ($request->sort) {
            if ($request->sort === 'new') $query->orderBy('created_at','desc');
            elseif ($request->sort === 'name') $query->orderBy('name');
            elseif ($request->sort === 'type') $query->orderBy('type');
            elseif ($request->sort === 'specialty') $query->orderBy('specialty');
            // Tri sur delegate non direct, ignore ici
        }

        $perPage = $request->per_page ?: 10;
        $listings = $query->paginate($perPage)->withQueryString();

        return view('pages.listings.index', [
            'listings' => $listings,
            'cities' => City::all(),
            'delegates' => User::all(),
            'types' => self::TYPES,
            'specialties' => self::SPECIALTIES
        ]);
    }

    /** Formulaire création */
    public function create()
    {
        return view('pages.listings.create', [
            'cities' => City::all(),
            'types' => self::TYPES,
            'specialties' => self::SPECIALTIES
        ]);
    }

    /** Enregistrer nouveau listing */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'type' => 'required|string|max:50',
            'specialty' => 'nullable|string|max:100',
            'address' => 'nullable|string|max:255',
            'city_id' => 'nullable|exists:cities,id',
            'lat' => 'nullable|numeric',
            'lng' => 'nullable|numeric',
        ]);

        Listing::create($validated);

        return redirect()->route('admin.listings.index')->with('success', 'Listing ajouté avec succès.');
    }

    /** Modifier un listing */
    public function edit(Listing $listing)
    {
        return view('pages.listings.edit', [
            'listing' => $listing,
            'cities' => City::all(),
            'types' => self::TYPES,
            'specialties' => self::SPECIALTIES
        ]);
    }

    /** Mettre à jour un listing */
    public function update(Request $request, Listing $listing)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'type' => 'required|string|max:50',
            'specialty' => 'nullable|string|max:100',
            'address' => 'nullable|string|max:255',
            'city_id' => 'nullable|exists:cities,id',
            'lat' => 'nullable|numeric',
            'lng' => 'nullable|numeric',
        ]);

        $listing->update($validated);

        return redirect()->route('admin.listings.index')->with('success', 'Listing mis à jour avec succès.');
    }

    /** Supprimer un listing */
    public function destroy(Listing $listing)
    {
        $listing->delete();

        return back()->with('success','Listing supprimé avec succès.');
    }
}
----- ./DeplacementController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Deplacement;
use App\Models\User;
use App\Models\City;

class DeplacementController extends Controller
{
    /** 🧭 Liste + filtres */
    public function index(Request $request)
    {
        $query = Deplacement::with('user');

        if ($search = $request->get('search')) {
            $query->where(function($q) use ($search) {
                $q->where('from_city','like',"%$search%")
                  ->orWhere('to_city','like',"%$search%");
            });
        }

        if ($status = $request->get('status')) {
            $query->where('status', $status);
        }

        if ($from = $request->get('from') and $to = $request->get('to')) {
            $query->whereBetween('date_depart', [$from, $to]);
        }

        $deplacements = $query->orderBy('date_depart','desc')->paginate(10)->withQueryString();

        return view('pages.deplacements.index', compact('deplacements'));
    }

    /** ➕ Formulaire création */
    public function create()
    {
        $users = User::orderBy('name')->get();
        $cities = City::orderBy('name')->get();
        return view('pages.deplacements.create', compact('users','cities'));
    }

    /** 💾 Sauvegarde */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'user_id' => 'required|exists:users,id',
            'from_city' => 'required|string|max:100',
            'to_city' => 'required|string|max:100',
            'distance_km' => 'nullable|numeric|min:0',
            'date_depart' => 'required|date',
            'status' => 'required|in:realise,non_realise',
        ]);

        Deplacement::create($validated);
        return redirect()->route('admin.deplacements.index')->with('success','Déplacement ajouté avec succès.');
    }

    /** ✏️ Modifier */
    public function edit(Deplacement $deplacement)
    {
        $users = User::orderBy('name')->get();
        $cities = City::orderBy('name')->get();
        return view('pages.deplacements.edit', compact('deplacement','users','cities'));
    }

    /** 🔄 Update */
    public function update(Request $request, Deplacement $deplacement)
    {
        $validated = $request->validate([
            'user_id' => 'required|exists:users,id',
            'from_city' => 'required|string|max:100',
            'to_city' => 'required|string|max:100',
            'distance_km' => 'nullable|numeric|min:0',
            'date_depart' => 'required|date',
            'status' => 'required|in:realise,non_realise',
        ]);

        $deplacement->update($validated);
        return redirect()->route('admin.deplacements.index')->with('success','Déplacement mis à jour.');
    }

    /** ❌ Supprimer */
    public function destroy(Deplacement $deplacement)
    {
        $deplacement->delete();
        return back()->with('success','Déplacement supprimé.');
    }

    /** 📄 Détails imprimables */
    public function show(Deplacement $deplacement)
    {
        $deplacement->load('user');
        return view('pages.deplacements.show', compact('deplacement'));
    }
}
----- ./TicketController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Visit;
use Illuminate\Http\Request;

class TicketController extends Controller
{
    /** Liste des visites avec ticket = true */
    public function index(Request $request)
    {
        $query = Visit::with(['user','listing','userAccompagnant'])
            ->where('has_ticket', true);

        if($search = $request->get('search')) {
            $query->whereHas('listing', fn($q) => $q->where('name','like',"%$search%"))
                  ->orWhereHas('user', fn($q) => $q->where('name','like',"%$search%"));
        }

        $visits = $query->orderBy('date_visit','desc')->paginate(15);

        return view('pages.tickets.index', compact('visits'));
    }

    /** Détail d’une visite */
    public function show(Visit $visit)
    {
        $visit->load(['user','listing','userAccompagnant']);
        return view('pages.tickets.show', compact('visit'));
    }

    /** Admin corrige les coordonnées et statut */
    public function update(Request $request, Visit $visit)
    {
        $validated = $request->validate([
            'poslat' => 'nullable|numeric',
            'poslong' => 'nullable|numeric',
            'status' => 'nullable|in:valide,invalide',
        ]);

        if(isset($validated['poslat']) && isset($validated['poslong'])) {
            // mettre nouvelles coordonnées et désactiver le ticket
            $visit->poslat = $validated['poslat'];
            $visit->poslong = $validated['poslong'];
            $visit->has_ticket = false;
        }

        if(isset($validated['status'])) {
            $visit->status = $validated['status'];
        }

        $visit->save();

        return redirect()->back()->with('success','Visite mise à jour avec succès.');
    }
}
----- ./CityController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\City;
use App\Models\User;
use Illuminate\Http\Request;

class CityController extends Controller
{
    /** Liste + filtre */
    public function index(Request $request)
    {
        $query = City::with('users')->orderBy('name');

        if ($region = $request->get('region')) {
            $query->where('region', $region);
        }

        $cities = $query->paginate(10)->withQueryString();

        // toutes les régions distinctes
        $regions = City::select('region')->distinct()->pluck('region');

        return view('pages.cities.index', compact('cities', 'regions'));
    }

    /** Formulaire création */
    public function create()
    {
        $regions = City::select('region')->distinct()->pluck('region');
        return view('pages.cities.create', compact('regions'));
    }

    /** Création */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255|unique:cities,name',
            'region' => 'required|string|max:255',
        ]);

        City::create($validated);

        return redirect()->route('admin.cities.index')->with('success', 'Secteur ajouté avec succès.');
    }

    /** Formulaire modification */
    public function edit(City $city)
    {
        $regions = City::select('region')->distinct()->pluck('region');
        return view('pages.cities.edit', compact('city', 'regions'));
    }

    /** Sauvegarde modification */
    public function update(Request $request, City $city)
    {
        $validated = $request->validate([
            'name'   => "required|string|max:255|unique:cities,name,{$city->id}",
            'region' => 'required|string|max:255',
        ]);

        $city->update($validated);

        return redirect()->route('admin.cities.index')->with('success', 'Secteur mis à jour avec succès.');
    }

    /** Supprimer */
    public function destroy(City $city)
    {
        $city->delete();
        return back()->with('success','Secteur supprimé.');
    }

    /** Attacher un ou plusieurs délégués à un secteur */
    public function attachDelegues(Request $request, City $city)
    {
        $validated = $request->validate([
            'users' => 'array|required',
            'users.*' => 'exists:users,id',
        ]);

        $city->users()->sync($validated['users']);

        return back()->with('success', 'Les délégués ont été mis à jour pour ce secteur.');
    }
}
----- ./VisitController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Visit;
use App\Models\User;
use App\Models\City;
use App\Models\Listing;
use App\Models\VisitInProgress;

use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\VisitsExport;

class VisitController extends Controller
{
    /** Index avec filtres */
  

public function index(Request $request)
{
    $query = Visit::with(['user.cities','listing']);

    // Filtre recherche sur client ou délégué
    if ($search = $request->get('search')) {
        $query->whereHas('listing', fn($q) => $q->where('name', 'like', "%$search%"))
              ->orWhereHas('user', fn($q) => $q->where('name', 'like', "%$search%"));
    }

    // Filtre type de visite
    if ($type = $request->get('visite_type')) {
        $query->where('visite_type', $type);
    }

    // Filtre statut
    if ($status = $request->get('status')) {
        $query->where('status', $status);
    }

    // Filtre délégué
    if ($user_id = $request->get('user_id')) {
        $query->where('user_id', $user_id);
    }

    // Filtre ville many-to-many via table pivot city_user
    if ($city_id = $request->get('city_id')) {
        $query->whereHas('user.cities', fn($q) => $q->where('cities.id', $city_id));
    }

    // Filtre plage de date
    if ($from = $request->get('from') and $to = $request->get('to')) {
        $query->whereBetween('date_visit', [$from, $to]);
    }

    // Pagination
    $visits = $query->orderBy('date_visit','desc')->paginate(15)->withQueryString();

    // Pour les selects
    $users = User::orderBy('name')->get();
    $cities = City::orderBy('name')->get();

    return view('pages.visits.index', compact('visits','users','cities'));
}
public function exportXls(Request $request)
{
    // On récupère les mêmes filtres que dans index()
    $filters = [
        'search'       => $request->get('search'),
        'visite_type'  => $request->get('visite_type'),
        'status'       => $request->get('status'),
        'from'         => $request->get('from'),
        'to'           => $request->get('to'),
    ];

    // On envoie les filtres à l’export
    return Excel::download(new VisitsExport($filters), 'visites_filtrees.xlsx');
}
    /** Page détail */
    public function show(Visit $visit)
    {
        $visit->load(['user','listing']);
        return view('pages.visits.show', compact('visit'));
    }

    /** Valider / invalider */
    public function updateStatus(Request $request, Visit $visit)
    {
        $validated = $request->validate([
            'status' => 'required|in:valide,invalide',
        ]);

        $visit->update(['status' => $validated['status']]);

        return back()->with('success', 'Statut de la visite mis à jour.');
    }
  public function progress(Request $request)
{
    $query = VisitInProgress::with([
        'user',
        'listing.city'
    ])->where('status', 'in_progress'); // 🔥 always in progress

    // 🔍 Recherche (délégué ou client)
    if ($request->filled('search')) {
        $search = $request->search;

        $query->where(function ($q) use ($search) {
            $q->whereHas('user', function ($q) use ($search) {
                $q->where('name', 'like', "%$search%");
            })->orWhereHas('listing', function ($q) use ($search) {
                $q->where('name', 'like', "%$search%");
            });
        });
    }

    // 👤 Filtre user
    if ($request->filled('user_id')) {
        $query->where('user_id', $request->user_id);
    }

    // 📍 Filtre secteur
    if ($request->filled('city_id')) {
        $query->whereHas('listing.city', function ($q) use ($request) {
            $q->where('id', $request->city_id);
        });
    }

    // 📅 Date début
    if ($request->filled('from')) {
        $query->whereDate('start_time', '>=', $request->from);
    }

    if ($request->filled('to')) {
        $query->whereDate('start_time', '<=', $request->to);
    }

    // 📦 Résultat
    $visits = $query->latest()->paginate(15);

    // filtres UI
    $users = \App\Models\User::all();
    $cities = \App\Models\City::all();

    return view('pages.visits.progress', compact('visits', 'users', 'cities'));
}
}
----- ./UserController.php -----
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Visit;
use App\Models\Location;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Illuminate\Support\Facades\Hash;


class UserController extends Controller
{
    /** 🧭 Liste + filtres */
    public function index(Request $request)
    {
        $query = User::query();

        // --- Filtres intelligents ---
        if ($search = $request->get('search')) {
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%$search%")
                  ->orWhere('email', 'like', "%$search%")
                  ->orWhere('code', 'like', "%$search%");
            });
        }

        if ($role = $request->get('role')) {
            $query->where('role', $role);
        }

        if ($status = $request->get('status')) {
            $query->where('is_active', $status === 'active');
        }

        $users = $query->orderBy('name')->paginate(10)->withQueryString();

        return view('pages.users.index', compact('users'));
    }

    /** 📄 Afficher détail A4 */
    public function show(User $user)
    {
        return view('pages.users.show', compact('user'));
    }

public function create()
{
    return view('pages.users.create');
}

public function store(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users,email',
        'password' => 'required|string|min:6|confirmed',
        'role' => 'required|in:admin,manager,delegue,visitor',
        'phone' => 'nullable|string|max:20',
        'mobile' => 'nullable|string|max:20',
        'address' => 'nullable|string|max:255',
        'department' => 'nullable|string|max:50',
        'hire_date' => 'nullable|date',
        'contract_type' => 'nullable|in:permanent,temporary,freelance',
        'is_active' => 'nullable|boolean',
    ]);

    $validated['password'] = Hash::make($validated['password']);

    $user = User::create($validated);

    return redirect()->route('admin.users.index')
        ->with('success', "L’utilisateur {$user->name} a bien été ajouté !");
}
public function edit(User $user)
{
    return view('pages.users.edit', compact('user'));
}

public function update(Request $request, User $user)
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users,email,' . $user->id,
        'role' => 'required|in:admin,manager,delegue,visitor',
        'phone' => 'nullable|string|max:20',
        'mobile' => 'nullable|string|max:20',
        'address' => 'nullable|string|max:255',
        'department' => 'nullable|string|max:50',
        'hire_date' => 'nullable|date',
        'contract_type' => 'nullable|in:permanent,temporary,freelance',
        'is_active' => 'nullable|boolean',
    ]);

    // Mise à jour simple
    $user->update($validated);

    return redirect()->route('admin.users.index')
        ->with('success', "L’utilisateur {$user->name} a été mis à jour avec succès !");
}public function tracking(Request $request, User $user)
{
    /*
    |--------------------------------------------------------------------------
    | FILTRE GLOBAL (DEFAULT = TODAY)
    |--------------------------------------------------------------------------
    */

    $period = $request->period ?? 'today';

    $start = null;
    $end   = null;

    switch ($period) {

        case 'today':
            $start = Carbon::today();
            $end   = Carbon::today()->endOfDay();
            break;

        case 'yesterday':
            $start = Carbon::yesterday();
            $end   = Carbon::yesterday()->endOfDay();
            break;

        case 'before_yesterday':
            $start = Carbon::today()->subDays(2);
            $end   = Carbon::today()->subDays(2)->endOfDay();
            break;

        case 'this_week':
            $start = Carbon::now()->startOfWeek();
            $end   = Carbon::now()->endOfWeek();
            break;

        case 'all':
            $start = null;
            $end   = null;
            break;
    }

    /*
    |--------------------------------------------------------------------------
    | FILTRE PERSONNALISÉ PRIORITAIRE
    |--------------------------------------------------------------------------
    */

    if ($request->start && $request->end) {
        $start = Carbon::parse($request->start);
        $end   = Carbon::parse($request->end);
    }

    /*
    |--------------------------------------------------------------------------
    | LOCATIONS
    |--------------------------------------------------------------------------
    */

    $locationsQuery = $user->locations()->orderBy('recorded_at');

    if ($start && $end) {
        $locationsQuery->whereBetween('recorded_at', [$start, $end]);
    }

    $locations = $locationsQuery->get();

    /*
    |--------------------------------------------------------------------------
    | VISITS (FILTRÉES AUSSI)
    |--------------------------------------------------------------------------
    */

    $visitsQuery = Visit::with(['listing', 'cycle'])
        ->where('user_id', $user->id);

    if ($start && $end) {
        $visitsQuery->whereBetween('date_visit', [$start, $end]);
    }

    $visits = $visitsQuery->latest()->get();

    /*
    |--------------------------------------------------------------------------
    | CALCUL ARRÊTS (> 5 MIN)
    |--------------------------------------------------------------------------
    */

    $disconnects = [];

    for ($i = 1; $i < count($locations); $i++) {

        $prev = $locations[$i - 1]->recorded_at;
        $current = $locations[$i]->recorded_at;

        $diffInSeconds = $prev->diffInSeconds($current);

        if ($diffInSeconds > 300) {

            $duration = Carbon::createFromTimestamp($diffInSeconds)->utc();

            $disconnects[] = [
                'from' => $prev,
                'to' => $current,
                'duration' => $duration->format('j\j H\h i\m s\s'),
                'duration_seconds' => $diffInSeconds // utile si tu veux stats plus tard
            ];
        }
    }

    /*
    |--------------------------------------------------------------------------
    | STATS
    |--------------------------------------------------------------------------
    */

    $startTime = optional($locations->first())->recorded_at;
    $endTime   = optional($locations->last())->recorded_at;

    $durationFormatted = null;

    if ($startTime && $endTime) {
        $durationFormatted = $startTime->diff($endTime)->format('%h h %i min');
    }

    /*
    |--------------------------------------------------------------------------
    | KPI VISITES
    |--------------------------------------------------------------------------
    */

    $kpiByListingType = $visits
        ->filter(fn($v) => $v->listing)
        ->groupBy(fn($v) => $v->listing->type);

    /*
    |--------------------------------------------------------------------------
    | RETURN
    |--------------------------------------------------------------------------
    */

    return view('pages.users.tracking', compact(
        'user',
        'locations',
        'disconnects',
        'startTime',
        'endTime',
        'durationFormatted',
        'visits',
        'kpiByListingType',
        'period',
        'start',
        'end'
    ));
}
}
----- ./SalaryController.php -----
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Visit;
use App\Models\Cycle;
use App\Models\DelegueSalary;
use App\Models\SalarySetting;
use Illuminate\Http\Request;

class SalaryController extends Controller
{
    /** Afficher les salaires par cycle */
    public function index(Request $request)
    {
        $cycles = Cycle::orderByDesc('start_date')->get();
        $currentCycle = $request->get('cycle_id', $cycles->first()?->id ?? null);

        $setting = SalarySetting::first();
        $primePerVisit = $setting?->prime_per_visit ?? 10;

        $delegues = User::all();

        $salaires = $delegues->map(function ($del) use ($primePerVisit, $currentCycle) {
            $fixe = DelegueSalary::where('user_id',$del->id)->value('base_salary') ?? 0;
            $visites = Visit::where('user_id',$del->id)
                            ->where('status','valide')
                            ->when($currentCycle, fn($q)=>$q->where('cycle_id',$currentCycle))
                            ->count();
            $prime = $visites * $primePerVisit;
            return [
                'delegue'=>$del,
                'fixe'=>$fixe,
                'visites'=>$visites,
                'prime'=>$prime,
                'total'=>$fixe+$prime,
            ];
        });

        return view('pages.salaries.index', compact('cycles','currentCycle','salaires','primePerVisit'));
    }

    /** Modifier salaire fixe délégué */
    public function updateFixed(Request $request, User $user)
    {
        $validated = $request->validate(['base_salary'=>'required|numeric|min:0']);
        DelegueSalary::updateOrCreate(
            ['user_id'=>$user->id],
            ['base_salary'=>$validated['base_salary']]
        );
        return back()->with('success','Salaire fixe modifié.');
    }

    /** Modifier la prime globale par visite */
    public function updatePrime(Request $request)
    {
        $validated = $request->validate(['prime_per_visit'=>'required|numeric|min:0']);
        SalarySetting::updateOrCreate(['id'=>1],['prime_per_visit'=>$validated['prime_per_visit']]);
        return back()->with('success','Prime par visite mise à jour.');
    }

    /** Détail d’un délégué */
    public function show(User $user, Request $request)
    {
        $cycles = Cycle::orderByDesc('start_date')->get();
        $currentCycle = $request->get('cycle_id', $cycles->first()?->id ?? null);

        $setting = SalarySetting::first();
        $primePerVisit = $setting?->prime_per_visit ?? 10;

        $fixe = DelegueSalary::where('user_id',$user->id)->value('base_salary') ?? 0;
        $visites = Visit::where('user_id',$user->id)
                        ->where('status','valide')
                        ->when($currentCycle, fn($q)=>$q->where('cycle_id',$currentCycle))
                        ->get();
        $prime = $visites->count() * $primePerVisit;
        $total = $fixe + $prime;

        return view('pages.salaries.show', compact('user','cycles','currentCycle','fixe','visites','prime','total','primePerVisit'));
    }
}