===== STRUCTURE =====
.
├── 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
├── output.txt
├── PermissionController.php
├── ProductController.php
├── UserController.php
├── VisitController.php
├── VisitInProgressController.php
├── VisitProductController.php
├── VisitTypeController.php
└── WeeklyProgramController.php

1 directory, 22 files

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

----- ./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());
    }
}
----- ./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]);
    }
}
----- ./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,
        ]);
    }
}
----- ./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);
    }
}
----- ./output.txt -----

----- ./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)
    {
        //
    }
}

----- ./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]);
    }
}
----- ./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);
        }
    }
}
----- ./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
    ]);
}
}

----- ./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
        ]);
    }
}
----- ./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)
    {
        //
    }
}

----- ./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());
    }
}
----- ./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);
    }
}
}

----- ./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)
    {
        //
    }
}

----- ./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(),
        ]);
    }
}
----- ./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)
    {
        //
    }
}

----- ./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
        ]);
    }
}
----- ./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)
    {
        //
    }
}

----- ./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é']);
    }
}
----- ./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
        ]);
    }
}
----- ./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)]);
    }
}
----- ./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
        ]);
    }
}