===== STRUCTURE ===== . ├── CityController.php ├── DashboardController.php ├── DeplacementController.php ├── ListingController.php ├── output.txt ├── PermissionController.php ├── SalaryController.php ├── TicketController.php ├── UserController.php └── VisitController.php 1 directory, 10 files ===== CONTENU DES FICHIERS ===== ----- ./PermissionController.php ----- orderByDesc('start_date'); if ($search = $request->get('search')) { $query->whereHas('user', fn($q)=>$q->where('name','like',"%$search%")); } if ($statut = $request->get('status')) { $query->where('status',$statut); } if ($reason = $request->get('reason')) { $query->where('reason',$reason); } if ($from = $request->get('from') and $to = $request->get('to')) { $query->whereBetween('start_date', [$from,$to]); } $permissions = $query->paginate(10)->withQueryString(); return view('pages.permissions.index', compact('permissions')); } public function show(Permission $permission) { $permission->load('user'); return view('pages.permissions.show', compact('permission')); } public function updateStatus(Request $request, Permission $permission) { $validated = $request->validate(['status'=>'required|in:accepté,en_cours,refus']); $permission->update(['status'=>$validated['status']]); return back()->with('success','Statut mis à jour.'); } } ----- ./output.txt ----- ----- ./DashboardController.php ----- count(); $totalVisits = Visit::count(); $validVisits = Visit::where('status', 'valide')->count(); $totalDeplacements = Deplacement::count(); $deplacementsEnCours = Deplacement::where('status', 'en_cours')->count(); $totalPermissions = Permission::count(); $permissionsPending = Permission::where('status', 'en_cours')->count(); // ========================= // VISITES PAR STATUT // ========================= $visitesByStatus = Visit::select('status', DB::raw('COUNT(*) as count')) ->groupBy('status') ->pluck('count', 'status') ->toArray(); // Garantir les clés $allStatuses = ['valide', 'invalide', 'double', 'non_realisee']; foreach ($allStatuses as $status) { if (!isset($visitesByStatus[$status])) { $visitesByStatus[$status] = 0; } } // ========================= // TOP DÉLÉGUÉS // ========================= $topDelegues = User::select('users.id', 'users.name', DB::raw('COUNT(visits.id) as total')) ->join('visits', 'users.id', '=', 'visits.user_id') ->where('visits.status', 'valide') ->groupBy('users.id', 'users.name') ->orderByDesc('total') ->take(5) ->get(); // ========================= // ÉVOLUTION SUR 12 MOIS // ========================= $rawVisitsPerMonth = Visit::select( DB::raw("YEAR(date_visit) as year"), DB::raw("MONTH(date_visit) as month"), DB::raw("COUNT(*) as total") ) ->where('date_visit', '>=', $now->copy()->subMonths(11)->startOfMonth()) ->groupBy('year', 'month') ->orderBy('year') ->orderBy('month') ->get() ->keyBy(function ($item) { return $item->year . '-' . str_pad($item->month, 2, '0', STR_PAD_LEFT); }); $visitsPerMonth = collect(); for ($i = 11; $i >= 0; $i--) { $date = $now->copy()->subMonths($i); $key = $date->format('Y-m'); $visitsPerMonth->push([ 'month' => $date->translatedFormat('M'), 'total' => $rawVisitsPerMonth[$key]->total ?? 0, ]); } // ========================= // ACTIVITÉS RÉCENTES // ========================= $recentVisits = Visit::with('user') ->latest('date_visit') ->take(5) ->get(); // ========================= // TAUX // ========================= $validVisitsRate = $totalVisits > 0 ? round(($validVisits / $totalVisits) * 100, 1) : 0; $activeUsersRate = $totalUsers > 0 ? round(($activeUsers / $totalUsers) * 100, 1) : 0; return view('pages.dashboard.admin-dashboard', compact( 'totalUsers', 'activeUsers', 'totalVisits', 'validVisits', 'totalDeplacements', 'deplacementsEnCours', 'totalPermissions', 'permissionsPending', 'visitesByStatus', 'topDelegues', 'visitsPerMonth', 'recentVisits', 'validVisitsRate', 'activeUsersRate' )); } } ----- ./ListingController.php ----- with('city.users'); if ($request->search) { $query->where(function($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%') ->orWhere('address', 'like', '%' . $request->search . '%'); }); } if ($request->type) $query->where('type', $request->type); if ($request->specialty) $query->where('specialty', $request->specialty); if ($request->city) $query->where('city_id', $request->city); if ($request->delegate) { $query->whereHas('city.users', fn($q) => $q->where('name', $request->delegate)); } if ($request->sort) { if ($request->sort === 'new') $query->orderBy('created_at','desc'); elseif ($request->sort === 'name') $query->orderBy('name'); elseif ($request->sort === 'type') $query->orderBy('type'); elseif ($request->sort === 'specialty') $query->orderBy('specialty'); // Tri sur delegate non direct, ignore ici } $perPage = $request->per_page ?: 10; $listings = $query->paginate($perPage)->withQueryString(); return view('pages.listings.index', [ 'listings' => $listings, 'cities' => City::all(), 'delegates' => User::all(), 'types' => self::TYPES, 'specialties' => self::SPECIALTIES ]); } /** Formulaire création */ public function create() { return view('pages.listings.create', [ 'cities' => City::all(), 'types' => self::TYPES, 'specialties' => self::SPECIALTIES ]); } /** Enregistrer nouveau listing */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'type' => 'required|string|max:50', 'specialty' => 'nullable|string|max:100', 'address' => 'nullable|string|max:255', 'city_id' => 'nullable|exists:cities,id', 'lat' => 'nullable|numeric', 'lng' => 'nullable|numeric', ]); Listing::create($validated); return redirect()->route('admin.listings.index')->with('success', 'Listing ajouté avec succès.'); } /** Modifier un listing */ public function edit(Listing $listing) { return view('pages.listings.edit', [ 'listing' => $listing, 'cities' => City::all(), 'types' => self::TYPES, 'specialties' => self::SPECIALTIES ]); } /** Mettre à jour un listing */ public function update(Request $request, Listing $listing) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'type' => 'required|string|max:50', 'specialty' => 'nullable|string|max:100', 'address' => 'nullable|string|max:255', 'city_id' => 'nullable|exists:cities,id', 'lat' => 'nullable|numeric', 'lng' => 'nullable|numeric', ]); $listing->update($validated); return redirect()->route('admin.listings.index')->with('success', 'Listing mis à jour avec succès.'); } /** Supprimer un listing */ public function destroy(Listing $listing) { $listing->delete(); return back()->with('success','Listing supprimé avec succès.'); } } ----- ./DeplacementController.php ----- get('search')) { $query->where(function($q) use ($search) { $q->where('from_city','like',"%$search%") ->orWhere('to_city','like',"%$search%"); }); } if ($status = $request->get('status')) { $query->where('status', $status); } if ($from = $request->get('from') and $to = $request->get('to')) { $query->whereBetween('date_depart', [$from, $to]); } $deplacements = $query->orderBy('date_depart','desc')->paginate(10)->withQueryString(); return view('pages.deplacements.index', compact('deplacements')); } /** ➕ Formulaire création */ public function create() { $users = User::orderBy('name')->get(); $cities = City::orderBy('name')->get(); return view('pages.deplacements.create', compact('users','cities')); } /** 💾 Sauvegarde */ public function store(Request $request) { $validated = $request->validate([ 'user_id' => 'required|exists:users,id', 'from_city' => 'required|string|max:100', 'to_city' => 'required|string|max:100', 'distance_km' => 'nullable|numeric|min:0', 'date_depart' => 'required|date', 'status' => 'required|in:realise,non_realise', ]); Deplacement::create($validated); return redirect()->route('admin.deplacements.index')->with('success','Déplacement ajouté avec succès.'); } /** ✏️ Modifier */ public function edit(Deplacement $deplacement) { $users = User::orderBy('name')->get(); $cities = City::orderBy('name')->get(); return view('pages.deplacements.edit', compact('deplacement','users','cities')); } /** 🔄 Update */ public function update(Request $request, Deplacement $deplacement) { $validated = $request->validate([ 'user_id' => 'required|exists:users,id', 'from_city' => 'required|string|max:100', 'to_city' => 'required|string|max:100', 'distance_km' => 'nullable|numeric|min:0', 'date_depart' => 'required|date', 'status' => 'required|in:realise,non_realise', ]); $deplacement->update($validated); return redirect()->route('admin.deplacements.index')->with('success','Déplacement mis à jour.'); } /** ❌ Supprimer */ public function destroy(Deplacement $deplacement) { $deplacement->delete(); return back()->with('success','Déplacement supprimé.'); } /** 📄 Détails imprimables */ public function show(Deplacement $deplacement) { $deplacement->load('user'); return view('pages.deplacements.show', compact('deplacement')); } } ----- ./TicketController.php ----- where('has_ticket', true); if($search = $request->get('search')) { $query->whereHas('listing', fn($q) => $q->where('name','like',"%$search%")) ->orWhereHas('user', fn($q) => $q->where('name','like',"%$search%")); } $visits = $query->orderBy('date_visit','desc')->paginate(15); return view('pages.tickets.index', compact('visits')); } /** Détail d’une visite */ public function show(Visit $visit) { $visit->load(['user','listing','userAccompagnant']); return view('pages.tickets.show', compact('visit')); } /** Admin corrige les coordonnées et statut */ public function update(Request $request, Visit $visit) { $validated = $request->validate([ 'poslat' => 'nullable|numeric', 'poslong' => 'nullable|numeric', 'status' => 'nullable|in:valide,invalide', ]); if(isset($validated['poslat']) && isset($validated['poslong'])) { // mettre nouvelles coordonnées et désactiver le ticket $visit->poslat = $validated['poslat']; $visit->poslong = $validated['poslong']; $visit->has_ticket = false; } if(isset($validated['status'])) { $visit->status = $validated['status']; } $visit->save(); return redirect()->back()->with('success','Visite mise à jour avec succès.'); } } ----- ./CityController.php ----- orderBy('name'); if ($region = $request->get('region')) { $query->where('region', $region); } $cities = $query->paginate(10)->withQueryString(); // toutes les régions distinctes $regions = City::select('region')->distinct()->pluck('region'); return view('pages.cities.index', compact('cities', 'regions')); } /** Formulaire création */ public function create() { $regions = City::select('region')->distinct()->pluck('region'); return view('pages.cities.create', compact('regions')); } /** Création */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255|unique:cities,name', 'region' => 'required|string|max:255', ]); City::create($validated); return redirect()->route('admin.cities.index')->with('success', 'Secteur ajouté avec succès.'); } /** Formulaire modification */ public function edit(City $city) { $regions = City::select('region')->distinct()->pluck('region'); return view('pages.cities.edit', compact('city', 'regions')); } /** Sauvegarde modification */ public function update(Request $request, City $city) { $validated = $request->validate([ 'name' => "required|string|max:255|unique:cities,name,{$city->id}", 'region' => 'required|string|max:255', ]); $city->update($validated); return redirect()->route('admin.cities.index')->with('success', 'Secteur mis à jour avec succès.'); } /** Supprimer */ public function destroy(City $city) { $city->delete(); return back()->with('success','Secteur supprimé.'); } /** Attacher un ou plusieurs délégués à un secteur */ public function attachDelegues(Request $request, City $city) { $validated = $request->validate([ 'users' => 'array|required', 'users.*' => 'exists:users,id', ]); $city->users()->sync($validated['users']); return back()->with('success', 'Les délégués ont été mis à jour pour ce secteur.'); } } ----- ./VisitController.php ----- get('search')) { $query->whereHas('listing', fn($q) => $q->where('name', 'like', "%$search%")) ->orWhereHas('user', fn($q) => $q->where('name', 'like', "%$search%")); } // Filtre type de visite if ($type = $request->get('visite_type')) { $query->where('visite_type', $type); } // Filtre statut if ($status = $request->get('status')) { $query->where('status', $status); } // Filtre délégué if ($user_id = $request->get('user_id')) { $query->where('user_id', $user_id); } // Filtre ville many-to-many via table pivot city_user if ($city_id = $request->get('city_id')) { $query->whereHas('user.cities', fn($q) => $q->where('cities.id', $city_id)); } // Filtre plage de date if ($from = $request->get('from') and $to = $request->get('to')) { $query->whereBetween('date_visit', [$from, $to]); } // Pagination $visits = $query->orderBy('date_visit','desc')->paginate(15)->withQueryString(); // Pour les selects $users = User::orderBy('name')->get(); $cities = City::orderBy('name')->get(); return view('pages.visits.index', compact('visits','users','cities')); } public function exportXls(Request $request) { // On récupère les mêmes filtres que dans index() $filters = [ 'search' => $request->get('search'), 'visite_type' => $request->get('visite_type'), 'status' => $request->get('status'), 'from' => $request->get('from'), 'to' => $request->get('to'), ]; // On envoie les filtres à l’export return Excel::download(new VisitsExport($filters), 'visites_filtrees.xlsx'); } /** Page détail */ public function show(Visit $visit) { $visit->load(['user','listing']); return view('pages.visits.show', compact('visit')); } /** Valider / invalider */ public function updateStatus(Request $request, Visit $visit) { $validated = $request->validate([ 'status' => 'required|in:valide,invalide', ]); $visit->update(['status' => $validated['status']]); return back()->with('success', 'Statut de la visite mis à jour.'); } public function progress(Request $request) { $query = VisitInProgress::with([ 'user', 'listing.city' ])->where('status', 'in_progress'); // 🔥 always in progress // 🔍 Recherche (délégué ou client) if ($request->filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { $q->whereHas('user', function ($q) use ($search) { $q->where('name', 'like', "%$search%"); })->orWhereHas('listing', function ($q) use ($search) { $q->where('name', 'like', "%$search%"); }); }); } // 👤 Filtre user if ($request->filled('user_id')) { $query->where('user_id', $request->user_id); } // 📍 Filtre secteur if ($request->filled('city_id')) { $query->whereHas('listing.city', function ($q) use ($request) { $q->where('id', $request->city_id); }); } // 📅 Date début if ($request->filled('from')) { $query->whereDate('start_time', '>=', $request->from); } if ($request->filled('to')) { $query->whereDate('start_time', '<=', $request->to); } // 📦 Résultat $visits = $query->latest()->paginate(15); // filtres UI $users = \App\Models\User::all(); $cities = \App\Models\City::all(); return view('pages.visits.progress', compact('visits', 'users', 'cities')); } } ----- ./UserController.php ----- get('search')) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%$search%") ->orWhere('email', 'like', "%$search%") ->orWhere('code', 'like', "%$search%"); }); } if ($role = $request->get('role')) { $query->where('role', $role); } if ($status = $request->get('status')) { $query->where('is_active', $status === 'active'); } $users = $query->orderBy('name')->paginate(10)->withQueryString(); return view('pages.users.index', compact('users')); } /** 📄 Afficher détail A4 */ public function show(User $user) { return view('pages.users.show', compact('user')); } public function create() { return view('pages.users.create'); } public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:6|confirmed', 'role' => 'required|in:admin,manager,delegue,visitor', 'phone' => 'nullable|string|max:20', 'mobile' => 'nullable|string|max:20', 'address' => 'nullable|string|max:255', 'department' => 'nullable|string|max:50', 'hire_date' => 'nullable|date', 'contract_type' => 'nullable|in:permanent,temporary,freelance', 'is_active' => 'nullable|boolean', ]); $validated['password'] = Hash::make($validated['password']); $user = User::create($validated); return redirect()->route('admin.users.index') ->with('success', "L’utilisateur {$user->name} a bien été ajouté !"); } public function edit(User $user) { return view('pages.users.edit', compact('user')); } public function update(Request $request, User $user) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email,' . $user->id, 'role' => 'required|in:admin,manager,delegue,visitor', 'phone' => 'nullable|string|max:20', 'mobile' => 'nullable|string|max:20', 'address' => 'nullable|string|max:255', 'department' => 'nullable|string|max:50', 'hire_date' => 'nullable|date', 'contract_type' => 'nullable|in:permanent,temporary,freelance', 'is_active' => 'nullable|boolean', ]); // Mise à jour simple $user->update($validated); return redirect()->route('admin.users.index') ->with('success', "L’utilisateur {$user->name} a été mis à jour avec succès !"); }public function tracking(Request $request, User $user) { /* |-------------------------------------------------------------------------- | FILTRE GLOBAL (DEFAULT = TODAY) |-------------------------------------------------------------------------- */ $period = $request->period ?? 'today'; $start = null; $end = null; switch ($period) { case 'today': $start = Carbon::today(); $end = Carbon::today()->endOfDay(); break; case 'yesterday': $start = Carbon::yesterday(); $end = Carbon::yesterday()->endOfDay(); break; case 'before_yesterday': $start = Carbon::today()->subDays(2); $end = Carbon::today()->subDays(2)->endOfDay(); break; case 'this_week': $start = Carbon::now()->startOfWeek(); $end = Carbon::now()->endOfWeek(); break; case 'all': $start = null; $end = null; break; } /* |-------------------------------------------------------------------------- | FILTRE PERSONNALISÉ PRIORITAIRE |-------------------------------------------------------------------------- */ if ($request->start && $request->end) { $start = Carbon::parse($request->start); $end = Carbon::parse($request->end); } /* |-------------------------------------------------------------------------- | LOCATIONS |-------------------------------------------------------------------------- */ $locationsQuery = $user->locations()->orderBy('recorded_at'); if ($start && $end) { $locationsQuery->whereBetween('recorded_at', [$start, $end]); } $locations = $locationsQuery->get(); /* |-------------------------------------------------------------------------- | VISITS (FILTRÉES AUSSI) |-------------------------------------------------------------------------- */ $visitsQuery = Visit::with(['listing', 'cycle']) ->where('user_id', $user->id); if ($start && $end) { $visitsQuery->whereBetween('date_visit', [$start, $end]); } $visits = $visitsQuery->latest()->get(); /* |-------------------------------------------------------------------------- | CALCUL ARRÊTS (> 5 MIN) |-------------------------------------------------------------------------- */ $disconnects = []; for ($i = 1; $i < count($locations); $i++) { $prev = $locations[$i - 1]->recorded_at; $current = $locations[$i]->recorded_at; $diffInSeconds = $prev->diffInSeconds($current); if ($diffInSeconds > 300) { $duration = Carbon::createFromTimestamp($diffInSeconds)->utc(); $disconnects[] = [ 'from' => $prev, 'to' => $current, 'duration' => $duration->format('j\j H\h i\m s\s'), 'duration_seconds' => $diffInSeconds // utile si tu veux stats plus tard ]; } } /* |-------------------------------------------------------------------------- | STATS |-------------------------------------------------------------------------- */ $startTime = optional($locations->first())->recorded_at; $endTime = optional($locations->last())->recorded_at; $durationFormatted = null; if ($startTime && $endTime) { $durationFormatted = $startTime->diff($endTime)->format('%h h %i min'); } /* |-------------------------------------------------------------------------- | KPI VISITES |-------------------------------------------------------------------------- */ $kpiByListingType = $visits ->filter(fn($v) => $v->listing) ->groupBy(fn($v) => $v->listing->type); /* |-------------------------------------------------------------------------- | RETURN |-------------------------------------------------------------------------- */ return view('pages.users.tracking', compact( 'user', 'locations', 'disconnects', 'startTime', 'endTime', 'durationFormatted', 'visits', 'kpiByListingType', 'period', 'start', 'end' )); } } ----- ./SalaryController.php ----- 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')); } }