===== STRUCTURE =====
.
├── Admin
│   ├── CityController.php
│   ├── DashboardController.php
│   ├── DeplacementController.php
│   ├── ListingController.php
│   ├── PermissionController.php
│   ├── SalaryController.php
│   ├── TicketController.php
│   ├── UserController.php
│   └── VisitController.php
├── API
│   ├── ActionRequestController.php
│   ├── AppointmentController.php
│   ├── AuthController.php
│   ├── CityController.php
│   ├── CycleController.php
│   ├── DashboardController.php
│   ├── DeplacementController.php
│   ├── ExpenseController.php
│   ├── ExpenseDashboardController.php
│   ├── ListingController.php
│   ├── LocationController.php
│   ├── NotificationController.php
│   ├── ObjectiveController.php
│   ├── PermissionController.php
│   ├── ProductController.php
│   ├── UserController.php
│   ├── VisitController.php
│   ├── VisitInProgressController.php
│   ├── VisitProductController.php
│   ├── VisitTypeController.php
│   └── WeeklyProgramController.php
├── Controller.php
├── ExpenseDashboardController.php
├── LocationController.php
├── output.txt
└── WorkSessionController.php

3 directories, 35 files

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

----- ./Admin/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.');
    }
}
----- ./Admin/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'
        ));
    }
}
----- ./Admin/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.');
    }
}
----- ./Admin/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'));
    }
}
----- ./Admin/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.');
    }
}
----- ./Admin/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.');
    }
}
----- ./Admin/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'));
}
}
----- ./Admin/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'
    ));
}
}
----- ./Admin/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'));
    }
}
----- ./API/ProductController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Product;

class ProductController extends Controller
{
    public function index()
    {
        return response()->json(Product::all());
    }
}
----- ./API/ExpenseController.php -----
<?php

namespace App\Http\Controllers\API;

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

class ExpenseController extends Controller
{
    public function index(Request $request)
    {
        return response()->json(
            Expense::where('user_id', $request->user()->id)->get()
        );
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'category' => 'required|string',
            'amount' => 'required|numeric',
            'description' => 'nullable|string',
        ]);

        $expense = Expense::create([
            'user_id' => $request->user()->id,
            ...$validated
        ]);

        return response()->json(['message' => 'Dépense enregistrée', 'data' => $expense]);
    }
}
----- ./API/AuthController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class AuthController extends Controller
{
    // Register new user
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name'       => 'required|string|max:255',
            'email'      => 'required|string|email|max:255|unique:users',
            'password'   => 'required|string|min:6|confirmed',
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $user = User::create([
            'name'     => $request->name,
            'email'    => $request->email,
            'password' => bcrypt($request->password),
        ]);

        return response()->json(['message' => 'User registered successfully', 'user' => $user], 201);
    }

    // Login user and return JWT token
    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (!$token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    // Get user profile
    public function profile()
    {
        return response()->json(auth('api')->user());
    }

    // Logout user (invalidate token)
    public function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    // Refresh JWT token
    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    // Return token response structure
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type'   => 'bearer',
            'expires_in'   => auth('api')->factory()->getTTL() * 60,
        ]);
    }
}
----- ./API/PermissionController.php -----
<?php

namespace App\Http\Controllers\API;

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

class PermissionController extends Controller
{
    /**
     * ✅ Liste des permissions de l'utilisateur connecté
     */
    public function index()
    {
        $permissions = Permission::where('user_id', auth()->id())
            ->orderByDesc('created_at')
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $permissions
        ]);
    }

    /**
     * ✅ Créer une nouvelle permission
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'start_date' => 'required|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'half_day' => 'boolean',
            'period' => 'nullable|in:matin,apres_midi',
            'reason' => 'required|in:trajet,reunion,congres,formation,maladie,affaire_personnel',
            'notes' => 'nullable|string',
        ]);

        $permission = Permission::create([
            'user_id' => auth()->id(),
            'start_date' => $validated['start_date'],
            'end_date' => $validated['end_date'] ?? null,
            'half_day' => $validated['half_day'] ?? false,
            'period' => $validated['period'] ?? null,
            'reason' => $validated['reason'],
            'notes' => $validated['notes'] ?? null,
            'status' => 'en_cours',
        ]);

        return response()->json([
            'status' => 'success',
            'message' => 'Demande envoyée avec succès',
            'data' => $permission
        ], 201);
    }
}
----- ./API/VisitProductController.php -----
<?php

namespace App\Http\Controllers\API;

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

class VisitProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}

----- ./API/NotificationController.php -----
<?php

namespace App\Http\Controllers\Api;

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

class NotificationController extends Controller
{
    // 📋 Liste notifications
    public function index(Request $request)
    {
        return Notification::where('user_id', $request->user()->id)
            ->orderByDesc('created_at')
            ->get();
    }

    // 🔴 Nombre non lues
    public function unreadCount(Request $request)
    {
        return response()->json([
            'count' => Notification::where('user_id', $request->user()->id)
                ->where('is_read', false)
                ->count()
        ]);
    }

    // ✅ Marquer une notification comme lue
    public function markAsRead($id, Request $request)
    {
        Notification::where('id', $id)
            ->where('user_id', $request->user()->id)
            ->update(['is_read' => true]);

        return response()->json(['success' => true]);
    }

    // ✅ Marquer toutes comme lues
    public function markAllAsRead(Request $request)
    {
        Notification::where('user_id', $request->user()->id)
            ->where('is_read', false)
            ->update(['is_read' => true]);

        return response()->json(['success' => true]);
    }
}
----- ./API/DashboardController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\{
    Visit,
    Listing,
    Cycle,
    Deplacement
};
use Exception;

class DashboardController extends Controller
{
    private const PRIME_PER_VISIT = 10; // DH

    public function index(Request $request)
    {
        try {
            $user = $request->user();
            if (!$user) {
                return response()->json([
                    'error' => 'Utilisateur non authentifié',
                    'code' => 401
                ], 401);
            }

            $now = Carbon::now();

            // 🔁 Cycle actuel
            $cycle = Cycle::whereDate('start_date', '<=', $now)
                ->whereDate('end_date', '>=', $now)
                ->first();

            if (!$cycle) {
                return response()->json([
                    'error' => 'Aucun cycle actif trouvé',
                    'code' => 404
                ], 404);
            }

            $cycleId = $cycle->id;

            // 🎯 Objectif = nombre de listings
            $cities = $user->cities()->pluck('city_id');
            if ($cities->isEmpty()) {
                return response()->json([
                    'error' => 'Aucune ville associée à cet utilisateur',
                    'code' => 422
                ], 422);
            }

            $objectif = Listing::whereIn('city_id', $cities)->count();
            if ($objectif === 0) {
                return response()->json([
                    'error' => 'Aucun listing trouvé pour les villes de l’utilisateur',
                    'code' => 404
                ], 404);
            }

            // ✅ Visites du cycle actuel
            $visitsInCycle = Visit::where('user_id', $user->id)
                ->where('cycle_id', $cycleId)
                ->count();

            // 💰 Gains (visites validées × 10 DH)
            $validatedVisits = Visit::where('user_id', $user->id)
                ->where('cycle_id', $cycleId)
                ->where('status', 'valide')
                ->count();

            $gains = $validatedVisits * self::PRIME_PER_VISIT;

            // 📊 Statistiques (tous les types)
            $listingTypes = [
                'PRIVE','PUBLIC','EMI-PUBLIC','MILITAIRE',
                'PHARMACIE','GROSSISTERIE','MATERNITE','URGENCE'
            ];

            $stats = collect($listingTypes)->mapWithKeys(function ($type) use ($user, $cycleId) {
                $count = Visit::where('user_id', $user->id)
                    ->where('cycle_id', $cycleId)
                    ->whereHas('listing', fn($q) => $q->where('type', $type))
                    ->count();
                return [$type => $count];
            });

            // 🕒 Activité récente (visites + déplacements)
            $recentVisits = Visit::with('listing')
                ->where('user_id', $user->id)
                ->select('id', 'listing_id', 'status', 'created_at')
                ->latest()
                ->take(5)
                ->get()
                ->map(fn($v) => [
                    'type' => 'visit',
                    'title' => $v->listing?->name ?? 'Listing supprimé',
                    'subtitle' => match ($v->status) {
                        'valide' => 'Visite validée',
                        'invalide' => 'Visite refusée',
                        default => 'Visite en attente',
                    },
                    'status' => $v->status === 'valide' ? 'success' : 'warning',
                    'date' => $v->created_at,
                ]);

            $recentTrips = Deplacement::where('user_id', $user->id)
                ->select('id', 'from_city', 'to_city', 'status', 'created_at')
                ->latest()
                ->take(5)
                ->get()
                ->map(fn($d) => [
                    'type' => 'deplacement',
                    'title' => "{$d->from_city} → {$d->to_city}",
                    'subtitle' => 'Déplacement ' . str_replace('_', ' ', $d->status),
                    'status' => $d->status === 'realise' ? 'success' : 'warning',
                    'date' => $d->created_at,
                ]);

            $recentActivity = $recentVisits
                ->merge($recentTrips)
                ->sortByDesc('date')
                ->take(5)
                ->values();

            // ✅ Response
            return response()->json([
                'user' => [
                    'name' => $user->name,
                    'date' => $now->translatedFormat('d F Y'),
                    'cities' => $cities,
                ],
                'kpis' => [
                    'visits' => $visitsInCycle,
                    'objectif' => $objectif,
                    'gains' => $gains . ' DH',
                ],
                'statistics' => $stats,
                'recent_activity' => $recentActivity,
            ]);

        } catch (Exception $e) {
            return response()->json([
                'error' => 'Une erreur est survenue : ' . $e->getMessage(),
                'code' => 500
            ], 500);
        }
    }
}
----- ./API/ListingController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Listing;
use App\Models\Visit;
use App\Models\Cycle;
use Carbon\Carbon;
use Illuminate\Validation\Rule;


class ListingController extends Controller
{
public function index(Request $request)
{
    $cityIds = $request->user()->cities->pluck('id');

    // Récupérer le cycle actuel
    $today = Carbon::today();
    $currentCycle = Cycle::where('start_date', '<=', $today)
                         ->where('end_date', '>=', $today)
                         ->first();

    $listings = Listing::with('city')
        ->whereIn('city_id', $cityIds)
        ->get();

    // Ajouter hasExist + trier
    $listings = $listings->map(function ($listing) use ($currentCycle) {
        $listing->cycle_name = $currentCycle?->name;
        $listing->hasExist = false;

        if ($currentCycle) {
            $listing->hasExist = Visit::where('listing_id', $listing->id)
                ->where('cycle_id', $currentCycle->id)
                ->exists();
        }

        // Forcer false pour certains types
        if ($listing->type === 'MATERNITE' || $listing->type === 'URGENCE') {
            $listing->hasExist = false;
        }

        return $listing;
    })
    ->sortBy([
        ['hasExist', 'asc'],        // false (0) avant true (1)
        ['created_at', 'desc'],     // plus récent en premier
    ])
    ->values();

    return response()->json($listings);
}

    public function show($id)
    {
        return response()->json(Listing::findOrFail($id));
    }

    public function updateCoordinates(Request $request, $id)
    {
        $listing = Listing::findOrFail($id);

        // Valider les données
        $request->validate([
            'lat' => 'nullable|numeric|between:-90,90',
            'lng' => 'nullable|numeric|between:-180,180',
        ]);

        // Mettre à jour uniquement lat et lng
        $listing->lat = $request->lat;
        $listing->lng = $request->lng;
        $listing->save();

        return response()->json([
            'message' => 'Coordinates updated successfully',
            'listing' => $listing
        ]);
    }


public function store(Request $request)
{
    $user = $request->user();

    $validated = $request->validate([
        'name'      => 'required|string|max:255',
        'address'   => 'required|string|max:255',
        'city_id'   => 'required|exists:cities,id',
        'type'      => 'nullable|string|max:100',
        'specialty' => 'nullable|string|max:150',
    ]);

    // ✅ Vérifier que la ville appartient à l'utilisateur
    if (! $user->cities()->where('cities.id', $validated['city_id'])->exists()) {
        return response()->json([
            'message' => 'Ville non autorisée'
        ], 403);
    }

    $listing = Listing::create($validated);

    return response()->json([
        'message' => 'Listing créé avec succès',
        'listing' => $listing
    ], 201);
}
public function update(Request $request, $id)
{
    $listing = Listing::findOrFail($id);

    $validated = $request->validate([
        'name'      => 'required|string|max:255',
        'address'   => 'required|string|max:255',
        'type'      => 'nullable|string|max:100',
        'specialty' => 'nullable|string|max:150',
    ]);

    $listing->update($validated);

    return response()->json([
        'message' => 'Listing modifié avec succès',
        'listing' => $listing
    ]);
}
}

----- ./API/DeplacementController.php -----
<?php

namespace App\Http\Controllers\API;

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

class DeplacementController extends Controller
{
    /**
     * ✅ Liste des déplacements de l'utilisateur connecté
     */
    public function myDeplacements()
    {
        $deplacements = Deplacement::where('user_id', auth()->id())
            ->orderBy('date_depart', 'desc')
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $deplacements
        ]);
    }

    /**
     * ✅ Mise à jour du statut
     * - non_realise → en_cours
     * - en_cours → realise
     * - en_cours → non_realise (annuler)
     */
    public function updateStatus(Request $request, $id)
    {
        $request->validate([
            'status' => 'required|in:non_realise,en_cours,realise',
        ]);

        $deplacement = Deplacement::where('id', $id)
            ->where('user_id', auth()->id())
            ->first();

        if (!$deplacement) {
            return response()->json([
                'status' => 'error',
                'message' => 'Déplacement non trouvé ou accès refusé'
            ], 404);
        }

        // ✅ Mise à jour
        $deplacement->status = $request->status;
        $deplacement->save();

        return response()->json([
            'status' => 'success',
            'message' => 'Statut mis à jour avec succès',
            'data' => $deplacement
        ]);
    }
}
----- ./API/VisitTypeController.php -----
<?php

namespace App\Http\Controllers\API;

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

class VisitTypeController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}

----- ./API/CityController.php -----
<?php

namespace App\Http\Controllers\API;

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

class CityController extends Controller
{
    // 🔹 Villes visibles pour le délégué connecté
    public function userCities(Request $request)
    {
        $cities = $request->user()->cities;
        return response()->json($cities);
    }

    // 🔹 Liste globale des villes (admin)
    public function index()
    {
        return response()->json(City::all());
    }
}
----- ./API/VisitController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Visit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\Cycle;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;

class VisitController extends Controller
{
    /**
     * Liste de toutes les visites
     */
    public function index(Request $request)
    {
        $cityIds = $request->user()->cities->pluck('id');

        $visits = Visit::with(['user', 'listing', 'cycle'])
            ->whereHas('listing', function ($query) use ($cityIds) {
                $query->whereIn('city_id', $cityIds);
            })
            ->latest()
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $visits
        ]);
    }

    public function valideVisits(Request $request)
    {
        $cityIds = $request->user()->cities->pluck('id');

        $visits = Visit::with(['user', 'listing', 'cycle'])
            ->where('status', 'valide')
            ->whereHas('listing', function ($query) use ($cityIds) {
                $query->whereIn('city_id', $cityIds);
            })
            ->latest()
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $visits
        ]);
    }

    public function invalideVisits()
    {
        $visits = Visit::with(['user', 'listing', 'cycle'])
            ->where('status', 'invalide')
            ->latest()
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $visits
        ]);
    }

    public function doubleVisits()
    {
        $visits = Visit::with(['user', 'listing', 'cycle'])
            ->where('status', 'double')
            ->latest()
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $visits
        ]);
    }

    public function nonRealiseeVisits()
    {
        $visits = Visit::with(['user', 'listing', 'cycle'])
            ->where('status', 'non_realisee')
            ->latest()
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $visits
        ]);
    }

    /**
     * Enregistrer une nouvelle visite
     */

  public function store(Request $request)
{
    // 1️⃣ Validation
    $validator = Validator::make($request->all(), [
        'listing_id' => 'required|integer|exists:listings,id',
        'visite_type' => 'required|string|in:Face à face,Double,Accompagné',
        'user_latitude' => 'nullable|numeric',
        'user_longitude' => 'nullable|numeric',
        'distance_meters' => 'nullable|numeric',
        'admin_pending' => 'nullable|boolean',
        'interlocuteur' => 'nullable|string|max:255',
        'role' => 'nullable|string|max:255',
        'rupture_stock' => 'nullable|boolean',
        'marche_direct' => 'nullable|boolean',
        'objection' => 'nullable|boolean',
        'important' => 'nullable|boolean',
        'elapsed_time' => 'nullable|integer|min:0',
        'proposed_meds' => 'nullable|array',
        'requested_meds' => 'nullable|array',
        'results' => 'nullable|array',
        'notes' => 'nullable|string',
        'status' => 'nullable|in:valide,invalide,double,non_realisee,distante',
        'user_accompagnant_id' => 'nullable|integer|exists:users,id',
        'poslat' => 'nullable|numeric',
        'poslong' => 'nullable|numeric',
        'has_ticket' => 'nullable|boolean',
        'is_far' => 'nullable|boolean',
        'far_reason' => 'nullable|string|max:255',
        'far_explanation' => 'nullable|string',
        // 'image' => 'nullable|string',
    ]);

    if ($validator->fails()) {
        return response()->json([
            'status' => 'error',
            'message' => 'Validation failed',
            'errors' => $validator->errors(),
        ], 422);
    }

    $data = $validator->validated();
    $today = Carbon::today();
    $thirtyMinutesAgo = Carbon::now()->subMinutes(30);

    $alreadyExists = Visit::where('user_id', auth()->id())
        ->where('listing_id', $data['listing_id'])
        ->where('date_visit', '>=', $thirtyMinutesAgo)
        ->exists();
    if ($alreadyExists) {
        return response()->json([
            'status' => 'error',
            'message' => 'Une visite a déjà été enregistrée il y a quelque minutes'
        ], 422);
    }

    // 3️⃣ Assigner les valeurs calculées
    $data['status'] = 'valide';
    if (!empty($request->user_position) && is_array($request->user_position)) {
        $data['poslat'] = $request->user_position['latitude'] ?? null;
        $data['poslong'] = $request->user_position['longitude'] ?? null;
    }

    // 4️⃣ Récupérer le cycle du jour
    $cycle = Cycle::where('start_date', '<=', $today)
        ->where('end_date', '>=', $today)
        ->first();

    if (!$cycle) {
        return response()->json([
            'status' => 'error',
            'message' => 'Aucun cycle trouvé pour aujourd’hui',
        ], 422);
    }

    $data['cycle_id'] = $cycle->id;
    $data['user_id'] = auth()->id();
    $data['date_visit'] = now();

    // 5️⃣ Gestion de l'image
    if (!empty($data['image']) && Str::startsWith($data['image'], 'data:image')) {
        $imageData = explode(',', $data['image'])[1];
        $imageName = Str::uuid() . '.png';
        Storage::disk('public')->put('visits/' . $imageName, base64_decode($imageData));
        $data['image'] = 'visits/' . $imageName;
    }

    // 6️⃣ Création de la visite
    $visit = Visit::create($data);

    return response()->json([
        'status' => 'success',
        'message' => 'Visite enregistrée avec succès',
        'data' => $visit,
    ], 201);
} // ✅ GET d'une visite avec nouvelles colonnes
    public function show($id)
    {
        $visit = Visit::with(['user', 'listing', 'cycle'])->find($id);

        if (!$visit) {
            return response()->json([
                'status' => 'error',
                'message' => 'Visite non trouvée'
            ], 404);
        }

        return response()->json([
            'status' => 'success',
            'data' => $visit
        ]);
    }

    public function update(Request $request, $id)
    {
        $visit = Visit::find($id);
        if (!$visit) {
            return response()->json([
                'status' => 'error',
                'message' => 'Visite non trouvée'
            ], 404);
        }

        $validator = Validator::make($request->all(), [
            // mêmes règles que pour store
            'listing_id' => 'integer|exists:listings,id',
            'visite_type' => 'string|in:Face à face,Double,Accompagné',
            'user_latitude' => 'nullable|numeric',
            'user_longitude' => 'nullable|numeric',
            'distance_meters' => 'nullable|numeric',
            'admin_pending' => 'boolean',
            'interlocuteur' => 'nullable|string|max:255',
            'role' => 'nullable|string|max:255',
            'rupture_stock' => 'boolean',
            'marche_direct' => 'boolean',
            'objection' => 'boolean',
            'important' => 'boolean',
            'elapsed_time' => 'integer|min:0',
            'proposed_meds' => 'nullable|array',
            'requested_meds' => 'nullable|array',
            'results' => 'nullable|array',
            'notes' => 'nullable|string',
            'status' => 'nullable|in:valide,invalide,double,non_realisee,distante',
            'user_accompagnant_id' => 'nullable|integer|exists:users,id',
            'poslat' => 'nullable|numeric',
            'poslong' => 'nullable|numeric',
            'has_ticket' => 'boolean',
            'is_far' => 'boolean',
            'far_reason' => 'nullable|string|max:255',
            'far_explanation' => 'nullable|string',
            // 'image' => 'nullable|string',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'status' => 'error',
                'message' => 'Validation failed',
                'errors' => $validator->errors(),
            ], 422);
        }

        $data = $validator->validated();

        // ✅ gérer l'image si base64
        if (!empty($data['image']) && Str::startsWith($data['image'], 'data:image')) {
            $imageData = explode(',', $data['image'])[1];
            $imageName = Str::uuid() . '.png';
            Storage::disk('public')->put('visits/' . $imageName, base64_decode($imageData));
            $data['image'] = 'visits/' . $imageName;
        }

        $visit->update($data);

        return response()->json([
            'status' => 'success',
            'message' => 'Visite mise à jour',
            'data' => $visit
        ]);
    }

 



    public function destroy($id)
    {
        $visit = Visit::find($id);

        if (!$visit) {
            return response()->json([
                'status' => 'error',
                'message' => 'Visite non trouvée'
            ], 404);
        }

        $visit->delete();

        return response()->json([
            'status' => 'success',
            'message' => 'Visite supprimée'
        ]);
    }

public function today(Request $request)
{
    try {
        $user = $request->user();
        if (!$user) {
            return response()->json([
                'status' => 'error',
                'message' => 'Utilisateur non authentifié'
            ], 401);
        }

        // ⚡ Préciser la table pour éviter l'ambiguïté
        $cityIds = $user->cities()->pluck('cities.id');
        if ($cityIds->isEmpty()) {
            return response()->json([
                'status' => 'error',
                'message' => 'Aucune ville associée à cet utilisateur'
            ], 422);
        }

        $today = Carbon::today();

        $stats = Visit::join('listings', 'visits.listing_id', '=', 'listings.id')
            ->whereDate('visits.date_visit', $today)
            ->whereIn('listings.city_id', $cityIds)
            ->selectRaw('listings.type, COUNT(*) as total')
            ->groupBy('listings.type')
            ->get();

        return response()->json([
            'status' => 'success',
            'data' => $stats
        ]);

    } catch (\Exception $e) {
        return response()->json([
            'status' => 'error',
            'message' => 'Une erreur est survenue : ' . $e->getMessage()
        ], 500);
    }
}
}

----- ./API/UserController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Hash;
use App\Models\User;



class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $connectedUserId = auth()->id();

        $users = User::select('id', 'name', 'email')
                ->where('id', '!=', $connectedUserId)
                ->get();
        return response()->json([
            'status' => 'success',
            'users' => $users
        ]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }
     public function updateProfile(Request $request)
    {
        $user = auth()->user(); 

       
        $validated = $request->validate([
            'name' => ['sometimes', 'string', 'max:255'],
            'email' => [
                'sometimes', 
                'email', 
                Rule::unique('users')->ignore($user->id)
            ],
            'phone' => ['nullable', 'string', 'max:20'],
            'mobile' => ['nullable', 'string', 'max:20'],
            'address' => ['nullable', 'string'],
            'birth_date' => ['nullable', 'date'],
            'national_id' => ['nullable', 'string', 'max:50'],
            'car_registration' => ['nullable', 'string', 'max:20'],
            'car_model' => ['nullable', 'string', 'max:50'],
            'password' => ['nullable', 'confirmed', 'min:8'], // inclure password_confirmation
        ]);

        if (!empty($validated['password'])) {
            $validated['password'] = Hash::make($validated['password']);
        } else {
            unset($validated['password']);
        }

        $user->update($validated);

        if (array_key_exists('password', $validated)) {
            $user->update([
                'password_changed_at' => now(),
                'must_change_password' => false
            ]);
        }

        return response()->json([
            'status' => 'success',
            'message' => 'Profil mis à jour avec succès.',
            'user' => $user,
        ]);
    }

 
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}

----- ./API/ExpenseDashboardController.php -----
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Visit;
use App\Models\Objective;
use App\Models\Expense;
use App\Models\SalarySetting;
use App\Models\Cycle;

class ExpenseDashboardController extends Controller
{
    public function index(Request $request)
    {
        $user = $request->user();

        /* ================= CYCLE ACTUEL ================= */
        $cycle = Cycle::whereDate('start_date', '<=', now())
            ->whereDate('end_date', '>=', now())
            ->first();

        $cycleId = optional($cycle)->id;

        /* ================= PRIME PAR VISITE ================= */
        $primePerVisit = SalarySetting::first()?->prime_per_visit ?? 0;

        /* ================= VISITES ================= */
        $visitsCount = Visit::where('user_id', $user->id)
            ->where('cycle_id', $cycleId)
            ->where('status', 'valide')
            ->count();

        $visitGain = $visitsCount * $primePerVisit;

        /* ================= DÉPLACEMENTS ================= */
        $deplacementGain = Expense::where('user_id', $user->id)
            ->where('category', 'transport')
            ->where('status', 'approuvee')
            ->sum('amount');

        /* ================= HORS TERRAIN ================= */
        $horsTerrainGain = Expense::where('user_id', $user->id)
            ->whereIn('category', ['repas', 'hotel'])
            ->where('status', 'approuvee')
            ->sum('amount');

        /* ================= AUTRES ================= */
        $autresGain = Expense::where('user_id', $user->id)
            ->where('category', 'autre')
            ->where('status', 'approuvee')
            ->sum('amount');

        /* ================= TOTAL ================= */
        $total = $visitGain + $deplacementGain + $horsTerrainGain + $autresGain;

        /* ================= STATS ================= */
        $stats = [
            [
                'label' => 'Visite',
                'gain' => round($visitGain),
                'percent' => $total > 0 ? round(($visitGain / $total) * 100) : 0,
            ],
            [
                'label' => 'Déplacement',
                'gain' => round($deplacementGain),
                'percent' => $total > 0 ? round(($deplacementGain / $total) * 100) : 0,
            ],
            [
                'label' => 'Hors terrain',
                'gain' => round($horsTerrainGain),
                'percent' => $total > 0 ? round(($horsTerrainGain / $total) * 100) : 0,
            ],
            [
                'label' => 'Autres',
                'gain' => round($autresGain),
                'percent' => $total > 0 ? round(($autresGain / $total) * 100) : 0,
            ],
        ];

        /* ================= OBJECTIFS ================= */
        $objectifs = Objective::where('user_id', $user->id)
            ->where('cycle_id', $cycleId)
            ->get()
            ->map(function ($obj) use ($primePerVisit) {

                $done = $obj->target_visits > 0
                    ? round(($obj->achieved / $obj->target_visits) * 100)
                    : 0;

                return [
                    'type' => ucfirst($obj->category),
                    'objectif' => $obj->target_visits,
                    'realise' => $obj->achieved,
                    'done' => $done,
                    'gain' => round($obj->achieved * $primePerVisit),
                ];
            });

        /* ================= RESPONSE ================= */
        return response()->json([
            'total' => round($total),
            'stats' => $stats,
            'objectifs' => $objectifs->values(),
        ]);
    }
}
----- ./API/ObjectiveController.php -----
<?php

namespace App\Http\Controllers\API;

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

class ObjectiveController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}

----- ./API/VisitInProgressController.php -----
<?php
namespace App\Http\Controllers\Api;

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

class VisitInProgressController extends Controller
{
    // 🔥 START VISIT
    public function start(Request $request)
    {
        $data = $request->validate([
            'listing_id' => 'required|exists:listings,id',
            'start_lat' => 'required|numeric',
            'start_lng' => 'required|numeric',
        ]);

        // éviter plusieurs visites en cours
        $existing = VisitInProgress::where('user_id', auth()->id())
            ->where('status', 'in_progress')
            ->first();

        if ($existing) {
            return response()->json([
                'status' => 'error',
                'message' => 'Une visite est déjà en cours'
            ], 400);
        }

        $visit = VisitInProgress::create([
            'user_id' => auth()->id(),
            'listing_id' => $data['listing_id'],
            'start_time' => now(),
            'start_lat' => $data['start_lat'],
            'start_lng' => $data['start_lng'],
            'status' => 'in_progress',
        ]);

        return response()->json([
            'status' => 'success',
            'visit' => $visit
        ]);
    }

    // 🧨 FINISH VISIT
    public function finish(Request $request)
    {
        $data = $request->validate([
            'end_lat' => 'required|numeric',
            'end_lng' => 'required|numeric',
        ]);

        $visit = VisitInProgress::where('user_id', auth()->id())
            ->where('status', 'in_progress')
            ->latest()
            ->first();

        if (!$visit) {
            return response()->json([
                'status' => 'error',
                'message' => 'Aucune visite en cours'
            ], 404);
        }

        $visit->update([
            'end_time' => now(),
            'end_lat' => $data['end_lat'],
            'end_lng' => $data['end_lng'],
            'status' => 'finished',
        ]);

        return response()->json([
            'status' => 'success',
            'visit' => $visit
        ]);
    }
}
----- ./API/WeeklyProgramController.php -----
<?php

namespace App\Http\Controllers\API;

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

class WeeklyProgramController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
}

----- ./API/AppointmentController.php -----
<?php

namespace App\Http\Controllers\API;

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

class AppointmentController extends Controller
{
    public function index(Request $request)
    {
        return response()->json(Appointment::where('user_id', $request->user()->id)->get());
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'listing_id' => 'required|integer',
            'date_appointment' => 'required|date'
        ]);

        $appt = Appointment::create([
            'user_id' => $request->user()->id,
            ...$validated
        ]);

        return response()->json(['message' => 'Rendez-vous ajouté', 'data' => $appt]);
    }

    public function destroy($id)
    {
        Appointment::findOrFail($id)->delete();
        return response()->json(['message' => 'Rendez-vous supprimé']);
    }
}
----- ./API/CycleController.php -----
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Cycle;
use App\Models\Objective;
use App\Models\WeeklyProgram;

class CycleController extends Controller {
    public function index() {
        return response()->json(Cycle::all());
    }
}

class ObjectiveController extends Controller {
    public function index() {
        return response()->json(Objective::with('cycle')->get());
    }
}

class WeeklyProgramController extends Controller {
    public function userProgram() {
        return response()->json(WeeklyProgram::with('cycle','city')->get());
    }
}

class DashboardController extends Controller {
    public function index() {
        return response()->json([
            'total_visits' => 38,
            'goal' => 50,
            'medications_presented' => 13,
            'earnings' => 2500
        ]);
    }
}
----- ./API/LocationController.php -----
<?php

namespace App\Http\Controllers\API;

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

class LocationController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric',
        ]);

        $location = Location::create([
            'user_id' => $request->user()->id,
            'latitude' => $request->latitude,
            'longitude' => $request->longitude,
            'recorded_at' => now(),
        ]);

        \Log::info("User {$request->user()->id} location saved: {$request->latitude}, {$request->longitude}");

        return response()->json([
            'status' => 'success',
            'location' => $location
        ]);
    }

    public function bulk(Request $request)
    {
        $locations = $request->input('locations', []);
        foreach ($locations as $loc) {
            Location::create([
                'user_id' => $request->user()->id,
                'latitude' => $loc['latitude'],
                'longitude' => $loc['longitude'],
                'recorded_at' => $loc['recorded_at'] ?? now(),
            ]);
        }

        return response()->json(['status' => 'success', 'count' => count($locations)]);
    }
}
----- ./API/ActionRequestController.php -----
<?php

namespace App\Http\Controllers\API;

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

class ActionRequestController extends Controller
{
    public function index(Request $request)
    {
        $actions = ActionRequest::with('user')
            ->where('user_id', auth()->id())
            ->when($request->search, function ($q) use ($request) {
                $q->where('produit', 'like', "%{$request->search}%")
                  ->orWhere('type', 'like', "%{$request->search}%");
            })
            ->orderByDesc('created_at')
            ->get();

        return response()->json([
            'data' => $actions
        ]);
    }
}
----- ./output.txt -----

----- ./WorkSessionController.php -----
<?php

namespace App\Http\Controllers;

use App\Models\WorkSession;
use Illuminate\Http\Request;

class WorkSessionController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(WorkSession $workSession)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, WorkSession $workSession)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(WorkSession $workSession)
    {
        //
    }
}

----- ./ExpenseDashboardController.php -----
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ExpenseDashboardController extends Controller
{
    //
}

----- ./LocationController.php -----
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LocationController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric',
            'recorded_at' => 'nullable|date'
        ]);

        Location::create([
            'user_id' => auth()->id(),
            'latitude' => $request->latitude,
            'longitude' => $request->longitude,
            'recorded_at' => $request->recorded_at ?? now(),
        ]);

        return response()->json(['success' => true]);
    }

    public function bulk(Request $request)
    {
        $locations = $request->locations;

        foreach ($locations as $loc) {
            Location::create([
                'user_id' => auth()->id(),
                'latitude' => $loc['latitude'],
                'longitude' => $loc['longitude'],
                'recorded_at' => $loc['recorded_at'],
            ]);
        }

        return response()->json(['success' => true]);
    }
}

----- ./Controller.php -----
<?php

namespace App\Http\Controllers;

abstract class Controller
{
    //
}
