diff --git a/.env.example b/.env copy.example similarity index 100% rename from .env.example rename to .env copy.example diff --git a/app/Http/Controllers/AdminClientController.php b/app/Http/Controllers/AdminClientController.php new file mode 100644 index 0000000..ac69196 --- /dev/null +++ b/app/Http/Controllers/AdminClientController.php @@ -0,0 +1,138 @@ +filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('nom', 'like', "%{$search}%") + ->where('prenom', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('telephone', 'like', "%{$search}%"); + }); + } + + // Filtre par activité + if ($request->filled('filter')) { + switch ($request->filter) { + case 'active': + $query->has('orders'); + break; + case 'inactive': + $query->doesntHave('orders'); + break; + } + } + + // Tri + $sortBy = $request->get('sort', 'created_at'); + $sortOrder = $request->get('order', 'desc'); + + if ($sortBy === 'orders_count') { + $query->orderBy('orders_count', $sortOrder); + } else { + $query->orderBy($sortBy, $sortOrder); + } + + $clients = $query->paginate(20); + + // Statistiques + $stats = [ + 'total' => User::count(), + 'with_orders' => User::has('orders')->count(), + 'without_orders' => User::doesntHave('orders')->count(), + 'this_month' => User::whereMonth('created_at', date('m')) + ->whereYear('created_at', date('Y')) + ->count(), + ]; + + return view('admin.layout.boutique.clients.index', compact('clients', 'stats')); + } + + /** + * Détails d'un client + */ + public function show($id) + { + $client = User::with([ + 'orders' => function($query) { + $query->with('products')->latest(); + }, + 'paiements' => function($query) { + $query->latest(); + } + ])->findOrFail($id); + + // Statistiques du client + $clientStats = [ + 'total_commandes' => $client->orders->count(), + 'montant_total' => $client->orders->sum('price_total_order'), + 'commande_moyenne' => $client->orders->count() > 0 + ? $client->orders->avg('price_total_order') + : 0, + 'derniere_commande' => $client->orders->first()?->created_at, + ]; + + return view('admin.layout.boutique.clients.show', compact('client', 'clientStats')); + } + + /** + * Exporter la liste des clients + */ + public function export() + { + $clients = User::withCount('orders')->get(); + + $filename = 'clients_' . date('Y-m-d') . '.csv'; + + $headers = [ + 'Content-Type' => 'text/csv', + 'Content-Disposition' => "attachment; filename={$filename}", + ]; + + $callback = function() use ($clients) { + $file = fopen('php://output', 'w'); + + // En-têtes + fputcsv($file, [ + 'ID', + 'Nom', + 'Prenom', + 'Email', + 'Téléphone', + 'Nombre de commandes', + 'Date d\'inscription' + ]); + + foreach ($clients as $client) { + fputcsv($file, [ + $client->id, + $client->nom, + $client->prenom, + $client->email, + $client->telephone ?? 'N/A', + $client->orders_count, + $client->created_at->format('d/m/Y') + ]); + } + + fclose($file); + }; + + return response()->stream($callback, 200, $headers); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index e97a17e..ba871d1 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -460,10 +460,8 @@ public function updateLesson(UpdateLessonRequest $request, $lessonId) ]); } - - public function destroyLesson($lessonId) + public function destroyLesson(Lesson $lesson) { - $lesson = Lesson::find($lessonId); $lesson->delete(); return response()->json([ @@ -502,6 +500,15 @@ public function Showpaiements() return view('admin.layout.formations.list_paiements', compact("paiements")); } + public function ShowPaiementsBoutique() + { + $paiements = Paiement::with(['user', 'commande']) + ->whereNotNull('order_id') + ->paginate(10); + + return view('admin.layout.boutique.list_paiements', compact('paiements')); + } + public function ShowCertifications() { diff --git a/app/Http/Controllers/AdminOrderController.php b/app/Http/Controllers/AdminOrderController.php new file mode 100644 index 0000000..3f9137e --- /dev/null +++ b/app/Http/Controllers/AdminOrderController.php @@ -0,0 +1,186 @@ +filled('status')) { + $query->where('status', $request->status); + } + + // Filtre par date + if ($request->filled('date_from')) { + $query->whereDate('created_at', '>=', $request->date_from); + } + if ($request->filled('date_to')) { + $query->whereDate('created_at', '<=', $request->date_to); + } + + // Recherche par code de commande ou nom client + if ($request->filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('order_code', 'like', "%{$search}%") + ->orWhereHas('user', function($userQuery) use ($search) { + $userQuery->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + }); + } + + $orders = $query->latest()->paginate(15); + + // Statistiques + $stats = [ + 'total' => Order::count(), + 'pending' => Order::pending()->count(), + 'confirmed' => Order::confirmed()->count(), + 'processing' => Order::processing()->count(), + 'shipped' => Order::shipped()->count(), + 'delivered' => Order::delivered()->count(), + ]; + + return view('admin.layout.boutique.orders.index', compact('orders', 'stats')); + } + + /** + * Détails d'une commande + */ + public function show($id) + { + $order = Order::with(['user', 'products', 'paiement'])->findOrFail($id); + + return view('admin.layout.boutique.orders.show', compact('order')); + } + + /** + * Changer le statut d'une commande + */ + public function updateStatus(Request $request, $id) + { + $request->validate([ + 'status' => 'required|in:pending,confirmed,processing,shipped,delivered,cancelled', + 'note' => 'nullable|string|max:500' + ]); + + $order = Order::findOrFail($id); + $oldStatus = $order->status; + $newStatus = $request->status; + + $order->update([ + 'status' => $newStatus + ]); + + // Envoyer une notification email au client + if ($newStatus === 'confirmed') { + try { + Mail::to($order->user->email)->send(new OrderStatusChanged($order, $oldStatus, $newStatus)); + } catch (\Exception $e) { + // Log l'erreur mais ne bloque pas le processus + \Log::error('Email notification failed: ' . $e->getMessage()); + } + } + + return back()->with('success', 'Statut de la commande mis à jour avec succès.'); + } + + /** + * Valider le paiement d'une commande + */ + public function validatePayment($id) + { + $order = Order::with('paiement')->findOrFail($id); + + if (!$order->paiement) { + return back()->with('error', 'Aucun paiement trouvé pour cette commande.'); + } + + $order->paiement->update([ + 'status' => 'completed' + ]); + + $order->update([ + 'status' => 'confirmed' + ]); + + // Envoyer notification + try { + Mail::to($order->user->email)->send(new OrderStatusChanged($order, 'pending', 'confirmed')); + } catch (\Exception $e) { + \Log::error('Email notification failed: ' . $e->getMessage()); + } + + return back()->with('success', 'Paiement validé et commande confirmée.'); + } + + /** + * Ajouter une note à la commande + */ + public function addNote(Request $request, $id) + { + $request->validate([ + 'note' => 'required|string|max:1000' + ]); + + $order = Order::findOrFail($id); + + // Vous pouvez créer une table 'order_notes' séparée + // Ou stocker dans un champ JSON + // Pour l'instant, on utilise un champ texte simple + + return back()->with('success', 'Note ajoutée avec succès.'); + } + + /** + * Exporter les commandes en CSV + */ + public function export(Request $request) + { + $orders = Order::with(['user', 'products'])->get(); + + $filename = 'commandes_' . date('Y-m-d') . '.csv'; + + $headers = [ + 'Content-Type' => 'text/csv', + 'Content-Disposition' => "attachment; filename={$filename}", + ]; + + $callback = function() use ($orders) { + $file = fopen('php://output', 'w'); + + // En-têtes + fputcsv($file, ['Code', 'Client', 'Email', 'Téléphone', 'Montant', 'Statut', 'Date']); + + foreach ($orders as $order) { + fputcsv($file, [ + $order->order_code, + $order->user->name, + $order->user->email, + $order->telephone, + $order->price_total_order . ' FCFA', + $order->statusLabel, + $order->created_at->format('d/m/Y H:i') + ]); + } + + fclose($file); + }; + + return response()->stream($callback, 200, $headers); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/AttestationController.php b/app/Http/Controllers/AttestationController.php new file mode 100644 index 0000000..888748c --- /dev/null +++ b/app/Http/Controllers/AttestationController.php @@ -0,0 +1,52 @@ +id) + ->where('formation_id', $id) + ->first(); + + if (!$userFormation || $userFormation->progression < 100) { + abort(403, 'Certificat non disponible. Formation non terminée.'); + } + + // Créer le dossier si inexistant + $directory = public_path('attestations'); + if (!file_exists($directory)) { + mkdir($directory, 0755, true); + } + + // Chemin du fichier PDF + $fileName = "attestation_{$formation->id}_{$user->id}.pdf"; + $filePath = $directory . DIRECTORY_SEPARATOR . $fileName; + + // Créer le PDF et l'enregistrer + $pdf = Pdf::loadView('attestation.pdf', [ + 'user' => $user, + 'formation' => $formation, + 'date' => now()->format('d/m/Y'), + ]); + $pdf->save($filePath); + + // Mettre à jour path_attestation si c'est null + if (is_null($userFormation->path_attestation)) { + $userFormation->path_attestation = "attestations/{$fileName}"; + $userFormation->save(); + } + + return response()->download($filePath); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 7919adb..b3c6ad9 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -39,8 +39,10 @@ public function store(RegisterRequest $request): RedirectResponse event(new Registered($user)); + + // Ne pas connecter l'utilisateur immédiatement Auth::login($user); - return redirect(route('dashboard', absolute: false)); + return redirect()->route('verification.notice')->with('message', 'Un email de confirmation a été envoyé. Veuillez vérifier votre boîte mail.'); } } diff --git a/app/Http/Controllers/CartController.php b/app/Http/Controllers/CartController.php new file mode 100644 index 0000000..a550836 --- /dev/null +++ b/app/Http/Controllers/CartController.php @@ -0,0 +1,156 @@ +getOrCreateCart(); + $cartItems = $cart->items()->with('product')->get(); + $total = $cart->calculateTotal(); + + return view('layouts.boutique.cart', compact('cart', 'cartItems', 'total')); + } + + /** + * Ajouter un produit au panier + */ + public function add(Request $request, $productId) + { + $request->validate([ + 'qte' => 'required|integer|min:1' + ]); + + $product = Product::findOrFail($productId); + $quantity = $request->qte; + + // Vérifier le stock + if (!$product->isInStock($quantity)) { + return back()->with('error', 'Stock insuffisant pour ce produit.'); + } + + $cart = $this->getOrCreateCart(); + + // Vérifier si le produit existe déjà dans le panier + $cartItem = CartItem::where('cart_id', $cart->id) + ->where('product_id', $productId) + ->first(); + + if ($cartItem) { + // Mettre à jour la quantité + $newQte = $cartItem->qte + $quantity; + + if (!$product->isInStock($newQte)) { + return back()->with('error', 'Stock insuffisant. Stock disponible: ' . $product->qte); + } + + $cartItem->update(['qte' => $newQte]); + } else { + // Créer un nouvel article dans le panier + CartItem::create([ + 'cart_id' => $cart->id, + 'product_id' => $productId, + 'qte' => $quantity + ]); + } + + // Recalculer le total + $cart->calculateTotal(); + + return back()->with('success', 'Produit ajouté au panier avec succès !'); + } + + /** + * Mettre à jour la quantité d'un article + */ + public function update(Request $request, $cartItemId) + { + $request->validate([ + 'qte' => 'required|integer|min:1' + ]); + + $cartItem = CartItem::findOrFail($cartItemId); + + // Vérifier que cet article appartient bien au panier de l'utilisateur + if ($cartItem->cart->user_id !== Auth::id()) { + return back()->with('error', 'Action non autorisée.'); + } + + $product = $cartItem->product; + $newQte = $request->qte; + + // Vérifier le stock + if (!$product->isInStock($newQte)) { + return back()->with('error', 'Stock insuffisant. Stock disponible: ' . $product->qte); + } + + $cartItem->update(['qte' => $newQte]); + $cartItem->cart->calculateTotal(); + + return back()->with('success', 'Quantité mise à jour.'); + } + + /** + * Supprimer un article du panier + */ + public function remove($cartItemId) + { + $cartItem = CartItem::findOrFail($cartItemId); + + // Vérifier que cet article appartient bien au panier de l'utilisateur + if ($cartItem->cart->user_id !== Auth::id()) { + return back()->with('error', 'Action non autorisée.'); + } + + $cart = $cartItem->cart; + $cartItem->delete(); + $cart->calculateTotal(); + + return back()->with('success', 'Produit retiré du panier.'); + } + + /** + * Vider complètement le panier + */ + public function clear() + { + $cart = $this->getOrCreateCart(); + $cart->clear(); + + return back()->with('success', 'Panier vidé avec succès.'); + } + + /** + * Récupérer ou créer le panier de l'utilisateur connecté + */ + private function getOrCreateCart() + { + $cart = Cart::firstOrCreate( + ['user_id' => Auth::id()], + ['total_price' => 0] + ); + + return $cart; + } + + /** + * Obtenir le nombre d'articles dans le panier (pour le header) + */ + public function count() + { + $cart = Cart::where('user_id', Auth::id())->first(); + $count = $cart ? $cart->totalItems() : 0; + + return response()->json(['count' => $count]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 73cf8ef..5289f69 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -24,4 +24,30 @@ public function store(Request $request) Category::create(['nom' => $request->nom]); return redirect()->route('admin.categories.index')->with('success', 'Catégorie ajoutée avec succès'); } + public function edit($id) +{ + $category = Category::findOrFail($id); + return response()->json($category); +} + +public function update(Request $request, $id) +{ + $request->validate([ + 'nom' => 'required|string|max:255' + ]); + + $category = Category::findOrFail($id); + $category->update(['nom' => $request->nom]); + + return response()->json(['success' => 'Catégorie mise à jour']); +} + + +public function destroy($id) +{ + $category = Category::findOrFail($id); + $category->delete(); + return redirect()->route('admin.categories.index')->with('success', 'Catégorie supprimée avec succès'); +} + } diff --git a/app/Http/Controllers/CheckoutController.php b/app/Http/Controllers/CheckoutController.php new file mode 100644 index 0000000..f55a33f --- /dev/null +++ b/app/Http/Controllers/CheckoutController.php @@ -0,0 +1,184 @@ +with('items.product')->first(); + + if (!$cart || $cart->items->isEmpty()) { + return redirect()->route('cart.index')->with('error', 'Votre panier est vide.'); + } + + // Vérifier la disponibilité de tous les produits + foreach ($cart->items as $item) { + if (!$item->product->isInStock($item->qte)) { + return redirect()->route('cart.index') + ->with('error', "Le produit '{$item->product->nom}' n'a plus assez de stock."); + } + } + + $subtotal = $cart->total_price; + $shippingFee = $this->calculateShipping($subtotal); + $total = $subtotal + $shippingFee; + + return view('layouts.boutique.checkout.index', compact('cart', 'subtotal', 'shippingFee', 'total')); + } + + /** + * Traiter la commande + */ + + + public function process(Request $request) + { + $request->validate([ + 'address' => 'required|string|max:500', + 'telephone' => 'required|string|max:20', + 'mode_livraison' => 'required|in:standard,express,retrait', + 'id' => 'required|string' // transaction id + ]); + + $cart = Cart::where('user_id', Auth::id())->with('items.product')->first(); + if (!$cart || $cart->items->isEmpty()) { + return redirect()->route('cart.index')->with('error', 'Votre panier est vide.'); + } + + foreach ($cart->items as $item) { + if (!$item->product->isInStock($item->qte)) { + return back()->with('error', "Le produit '{$item->product->nom}' n'est plus disponible."); + } + } + + try { + DB::beginTransaction(); + + $subtotal = $cart->total_price; + $shippingFee = $this->calculateShipping($subtotal, $request->mode_livraison); + $totalOrder = $subtotal + $shippingFee; + + // Création de la commande + $order = Order::create([ + 'user_id' => Auth::id(), + 'mode_livraison' => $request->mode_livraison, + 'status' => 'paid', + 'addresse' => $request->address, + 'telephone' => $request->telephone, + 'price_total_order' => $totalOrder, + ]); + + // Produits de la commande + foreach ($cart->items as $item) { + OrderProduct::create([ + 'order_id' => $order->id, + 'product_id' => $item->product_id, + 'qte_commander' => $item->qte + ]); + $item->product->decrementStock($item->qte); + } + + // ✅ Récupération de la transaction via PaymentService + $transaction = $this->paymentService->getTransaction($request->id); + + // ✅ Enregistrement du paiement + $paiement = Paiement::create([ + 'montant_payé' => $transaction->amount, + 'moyen_de_paiment' => $transaction->mode , + 'status' => $transaction->status=== 'approved' ? 'success':'failed', + 'user_id' => Auth::id(), + 'transaction_id'=>$request->id, + 'order_id' => $order->id, //success + ]); + + // ✅ Mise à jour de la commande selon + $order->status = $paiement->status === 'approved' ? 'paid' : 'pending'; + $order->save(); + + // ✅ Vider le panier + $cart->clear(); + + DB::commit(); + + return redirect()->route('checkout.success', $order->id) + ->with('success', 'Commande et paiement enregistrés avec succès !'); + } catch (\Exception $e) { + DB::rollBack(); + return back()->with('error', 'Erreur : ' . $e->getMessage())->withInput(); + } + } + + + + /** + * Page de confirmation après commande + */ + public function success($orderId) + { + $order = Order::with('products')->findOrFail($orderId); + + // Vérifier que la commande appartient à l'utilisateur connecté + if ($order->user_id !== Auth::id()) { + abort(403, 'Accès non autorisé.'); + } + + return view('checkout.success', compact('order')); + } + + /** + * Calculer les frais de livraison + */ + private function calculateShipping($subtotal, $mode = 'standard') + { + // Livraison gratuite au-dessus de 50 000 FCFA + if ($subtotal >= 50000) { + return 0; + } + + // Frais selon le mode de livraison + switch ($mode) { + case 'express': + return 3000; // Livraison express + case 'retrait': + return 0; // Retrait en magasin gratuit + case 'standard': + default: + return 1500; // Livraison standard + } + } + + /** + * API pour obtenir les frais de livraison en temps réel + */ + public function getShippingFee(Request $request) + { + $request->validate([ + 'mode_livraison' => 'required|in:standard,express,retrait', + 'subtotal' => 'required|numeric|min:0' + ]); + + $shippingFee = $this->calculateShipping($request->subtotal, $request->mode_livraison); + $total = $request->subtotal + $shippingFee; + + return response()->json([ + 'shipping_fee' => $shippingFee, + 'total' => $total + ]); + } +} diff --git a/app/Http/Controllers/FormationController.php b/app/Http/Controllers/FormationController.php index 64dd7a9..70ac16b 100644 --- a/app/Http/Controllers/FormationController.php +++ b/app/Http/Controllers/FormationController.php @@ -4,6 +4,7 @@ use App\Models\Formation; use App\Models\user_formation; +use App\Models\User_Quizz; use App\Models\UserLesson; use Illuminate\Support\Facades\Auth; @@ -11,15 +12,13 @@ class FormationController extends Controller { public function index() { - $formations = Formation::with(['modules.lessons']) - ->whereHas('users', function ($query) { - $query->where('user_id', Auth::id()); - }) + $formations = Formation::with(['modules.lessons', 'modules.quizz']) + ->whereHas('users', fn ($q) => $q->where('user_id', Auth::id())) ->get(); foreach ($formations as $formation) { - // Leçons totales de la formation - $totalLessons = $formation->modules->sum(fn ($module) => $module->lessons->count()); + // Total des leçons + $totalLessons = $formation->modules->sum(fn ($m) => $m->lessons->count()); // Leçons terminées par l'utilisateur $completedLessons = UserLesson::where('user_id', Auth::id()) @@ -27,63 +26,132 @@ public function index() ->where('terminee', true) ->count(); - // Calcul progression - $progress = $totalLessons > 0 ? ($completedLessons / $totalLessons) * 100 : 0; - - $formation->calculated_progress = round($progress, 2); + // Progression leçons (70%) + $lessonProgressPct = $totalLessons > 0 ? ($completedLessons / $totalLessons) * 100 : 0; + $lessonPart = $lessonProgressPct * 0.70; + + // Progression quizz (30%) + $allQuizz = $formation->modules->pluck('quizz')->filter(); + $quizCount = $allQuizz->count(); + $quizPart = 0; + + if ($quizCount > 0) { + $eachQuizWeight = 30 / $quizCount; + + foreach ($allQuizz as $quizz) { + $userQuizz = User_Quizz::where('user_id', Auth::id()) + ->where('quizz_id', $quizz->id) + ->first(); + + if ($userQuizz && $userQuizz->termine == 1) { + $quizPart += $eachQuizWeight; + } elseif ($userQuizz) { + $quizPart += ($userQuizz->score / 100) * $eachQuizWeight; + } + } + } + + if ($quizPart > 30) { + $quizPart = 30; + } + + $progress = floor($lessonPart + $quizPart); + $formation->calculated_progress = min($progress, 100); // max 100% + + // Vérifier attestation + $userFormation = user_formation::where('user_id', Auth::id()) + ->where('formation_id', $formation->id) + ->first(); + + $formation->attestation_status = ( + $formation->calculated_progress == 100 + && $userFormation + && $userFormation->path_attestation + ) ? 'disponible' : 'non_disponible'; } - $firstFormation = $formations->first(); + if (request()->ajax()) { + return view('formations.index', compact('formations')); + } - return view('formations.index', compact('formations', 'firstFormation')); + // Sinon, on charge la vue complète + return view('formations.index', compact('formations')); } public function show($id) { - $formation = Formation::with([ - 'objectifs', - 'modules.lessons', - 'modules.quizz.questions.reponses', - ])->findOrFail($id); - - $userFormation = user_formation::where('user_id', Auth::id()) - ->where('formation_id', $id) - ->first(); - - if (! $userFormation) { - return redirect()->route('formations.index')->with('error', 'Vous n\'êtes pas inscrit à cette formation.'); - } + $formation = Formation::with(['objectifs', 'modules.lessons', 'modules.quizz'])->findOrFail($id); + + $userFormation = user_formation::firstOrCreate([ + 'user_id' => Auth::id(), + 'formation_id' => $id, + ]); - // Calculer la progression par module $modulesWithProgress = []; foreach ($formation->modules as $module) { - $totalLessons = $module->lessons->count(); - $completedLessons = UserLesson::where('user_id', Auth::id()) + $total = $module->lessons->count(); + $completed = UserLesson::where('user_id', Auth::id()) ->whereIn('lesson_id', $module->lessons->pluck('id')) ->where('terminee', true) ->count(); - $moduleProgress = $totalLessons > 0 ? ($completedLessons / $totalLessons) * 100 : 0; + $moduleProgress = $total > 0 ? ($completed / $total) * 100 : 0; $modulesWithProgress[] = [ 'module' => $module, 'progress' => $moduleProgress, - 'completed_lessons' => $completedLessons, - 'total_lessons' => $totalLessons, + 'completed_lessons' => $completed, + 'total_lessons' => $total, ]; } - // Calcul progression globale (sur toute la formation) - $totalLessonsFormation = $formation->modules->sum(fn ($module) => $module->lessons->count()); - - $completedLessonsFormation = UserLesson::where('user_id', Auth::id()) + // progression globale des leçons + $totalLessons = $formation->modules->sum(fn ($m) => $m->lessons->count()); + $completedLessons = UserLesson::where('user_id', Auth::id()) ->whereIn('lesson_id', $formation->modules->flatMap(fn ($m) => $m->lessons->pluck('id'))) ->where('terminee', true) ->count(); - $globalProgress = $totalLessonsFormation > 0 ? ($completedLessonsFormation / $totalLessonsFormation) * 100 : 0; - $globalProgress = round($globalProgress, 2); + $lessonProgressPct = $totalLessons > 0 ? ($completedLessons / $totalLessons) * 100 : 0; + $lessonPart = $lessonProgressPct * 0.70; // 70% du poids total + + // progression globale des quiz (30% du poids total) + $allQuizz = $formation->modules->pluck('quizz')->filter(); + $quizCount = $allQuizz->count(); + $quizPart = 0; + + if ($quizCount > 0) { + $eachQuizWeight = 30 / $quizCount; + foreach ($allQuizz as $quizz) { + $userQuizz = User_Quizz::where('user_id', Auth::id()) + ->where('quizz_id', $quizz->id) + ->first(); + + if ($userQuizz && $userQuizz->termine == 1) { + $quizPart += $eachQuizWeight; + } elseif ($userQuizz) { + $quizPart += ($userQuizz->score / 100) * $eachQuizWeight; + } + } + } + if ($quizPart > 30) { + $quizPart = 30; + } + + $finalProgress = floor($lessonPart + $quizPart); + if ($finalProgress > 100) { + $finalProgress = 100; + } + + $userFormation->progression = $finalProgress; + $userFormation->status = ($finalProgress == 100) ? 'terminé' : 'en_attente'; + $userFormation->save(); - return view('formations.show', compact('formation', 'userFormation', 'modulesWithProgress', 'globalProgress')); + return view('formations.show', [ + 'formation' => $formation, + 'userFormation' => $userFormation, + 'modulesWithProgress' => $modulesWithProgress, + 'globalProgress' => $finalProgress, + ]); } } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index fe46c07..29d4ac1 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,28 +2,40 @@ namespace App\Http\Controllers; +use Illuminate\Http\Request; +use App\Models\Publication; +use App\Models\Product; use App\Models\Formation; use App\Models\user_formation; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class HomeController extends Controller { - // + + public function index() { + $latestPublications = Publication::with('category') + ->where('status', 'publish') + ->orderBy('created_at', 'desc') + ->take(3) + ->get(); + $produitsPopulaires = Product::where('qte', '>', 0) + ->orderBy('prix', 'desc') + ->limit(4) + ->get(); $formations = Formation::latest()->take(3)->get(); - return view('layouts.index', compact('formations')); - } + return view('layouts.index', compact('latestPublications', 'produitsPopulaires','formations')); + } + public function showFormations() { $formations = Formation::paginate(9); return view('layouts.formation.formation-catalog', compact('formations')); } - public function ShowOneFormation(Formation $formation) { $formation->load([ @@ -44,6 +56,36 @@ public function ShowOneFormation(Formation $formation) ->exists(); } - return view('layouts.formation.formation-detail', compact('formation', 'publicKey', 'user', 'isEnrolled')); + // ✅ Récupérer les 6 derniers avis avec les infos de l'utilisateur + $avisRecents = $formation->avis() + ->with('user') + ->latest() + ->take(6) + ->get(); + + return view('layouts.formation.formation-detail', compact( + 'formation', + 'publicKey', + 'user', + 'isEnrolled', + 'avisRecents' // 👉 On envoie les avis récents à la vue + )); } + public function AfficherTousLesAvis(Formation $formation) + { + $formation->load('avis.user'); + + // ✅ Pagination (10 avis par page) + $avis = $formation->avis() + ->with('user') + ->latest() + ->paginate(10); + + return view('formationS.avis', compact('formation', 'avis')); + } + } + + + + diff --git a/app/Http/Controllers/ModuleController.php b/app/Http/Controllers/ModuleController.php index 7d16f37..5f39e8d 100644 --- a/app/Http/Controllers/ModuleController.php +++ b/app/Http/Controllers/ModuleController.php @@ -5,6 +5,8 @@ use App\Models\Module; use App\Models\User_Quizz; use App\Models\UserLesson; +use App\Models\user_formation; +use App\Models\Lesson; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -23,7 +25,6 @@ public function show($formationId, $moduleId) $userLesson = UserLesson::where('user_id', Auth::id()) ->where('lesson_id', $lesson->id) ->first(); - $userProgress[$lesson->id] = $userLesson ? $userLesson->terminee : false; } @@ -47,32 +48,49 @@ public function completeLesson(Request $request, $lessonId) 'terminee_at' => now(), ]); - // Mettre à jour la progression globale de la formation $this->updateFormationProgress($lessonId); } - return response()->json(['success' => true]); + return redirect()->back(); } private function updateFormationProgress($lessonId) { - $lesson = \App\Models\Lesson::with('module.formation')->find($lessonId); - $formation = $lesson->module->formation; + $lesson = Lesson::with('module.formation.modules.lessons')->find($lessonId); + $module = $lesson->module; + $formation = $module->formation; + + $allLessons = $formation->modules->flatMap->lessons; + $totalLessons = $allLessons->count(); - $totalLessons = $formation->modules->flatMap->lessons->count(); $completedLessons = UserLesson::where('user_id', Auth::id()) - ->whereIn('lesson_id', $formation->modules->flatMap->lessons->pluck('id')) + ->whereIn('lesson_id', $allLessons->pluck('id')) ->where('terminee', true) ->count(); $progress = $totalLessons > 0 ? ($completedLessons / $totalLessons) * 100 : 0; + $progress = round($progress, 2); - $userFormation = \App\Models\UserFormation::where('user_id', Auth::id()) - ->where('formation_id', $formation->id) - ->first(); + $userFormation = user_formation::firstOrCreate([ + 'user_id' => Auth::id(), + 'formation_id' => $formation->id, + ]); + + $userFormation->progression = $progress; - if ($userFormation) { - $userFormation->update(['progression' => $progress]); + // Statut logique : si quiz existe et score 100 et progression 100 → terminé + $statut = 'en_attente'; + $quizz = $module->quizz; + if ($quizz) { + $userQuizz = User_Quizz::where('user_id', Auth::id()) + ->where('quizz_id', $quizz->id) + ->first(); + if ($userQuizz && round($userQuizz->score) == 100 && $progress == 100) { + $statut = 'terminé'; + } } + $userFormation->status = $statut; + + $userFormation->save(); } } diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php new file mode 100644 index 0000000..185e139 --- /dev/null +++ b/app/Http/Controllers/OrderController.php @@ -0,0 +1,141 @@ +with('products') + ->orderBy('created_at', 'desc') + ->paginate(10); + + return view('layouts.boutique.orders.index', compact('orders')); + } + + /** + * Afficher les détails d'une commande + */ + public function show($orderId) + { + $order = Order::with('products', 'paiement')->findOrFail($orderId); + + // Vérifier que la commande appartient à l'utilisateur + if ($order->user_id !== Auth::id()) { + abort(403, 'Accès non autorisé à cette commande.'); + } + + return view('layouts.boutique.orders.show', compact('order')); + } + + /** + * Annuler une commande (seulement si statut = pending ou confirmed) + */ + public function cancel($orderId) + { + $order = Order::with('products')->findOrFail($orderId); + + // Vérifier que la commande appartient à l'utilisateur + if ($order->user_id !== Auth::id()) { + abort(403, 'Accès non autorisé.'); + } + + // On ne peut annuler que les commandes en attente ou confirmées + if (!$order->canBeCancelled()) { + return back()->with('error', 'Cette commande ne peut plus être annulée (statut actuel : ' . $order->statusLabel . ')'); + } + + DB::transaction(function () use ($order) { + // Remettre les produits en stock + foreach ($order->products as $product) { + $quantity = $product->pivot->qte_commander; + $product->incrementStock($quantity); + } + + // Mettre à jour le statut + $order->update(['status' => 'cancelled']); + }); + + return back()->with('success', 'Commande annulée avec succès. Les produits ont été remis en stock.'); + } + + /** + * Suivi de commande publique (accessible même sans connexion) + */ + public function track(Request $request) + { + $order = null; + + if ($request->filled('order_code')) { + $order = Order::where('order_code', $request->order_code) + ->with(['products', 'user', 'paiement']) + ->first(); + + // Si l'utilisateur est connecté, vérifier que c'est bien sa commande + // Sinon on peut afficher uniquement des infos limitées + if ($order && Auth::check() && $order->user_id !== Auth::id()) { + $order = null; // Ne pas afficher la commande d'un autre utilisateur + } + } + + return view('layouts.boutique.order-tracking', compact('order')); + } + + /** + * Télécharger la facture (optionnel - nécessite DomPDF) + */ + public function downloadInvoice($orderId) + { + $order = Order::with('products', 'user', 'paiement')->findOrFail($orderId); + + // Vérifier que la commande appartient à l'utilisateur + if ($order->user_id !== Auth::id()) { + abort(403, 'Accès non autorisé.'); + } + + // Pour l'instant, on retourne juste une vue + // Plus tard, tu peux utiliser DomPDF pour générer un PDF + return view('orders.invoice', compact('order')); + } + + /** + * Recherche de commande (pour l'utilisateur connecté) + */ + public function search(Request $request) + { + $query = Order::where('user_id', Auth::id())->with('products'); + + // Recherche par code de commande + if ($request->filled('search')) { + $query->where('order_code', 'like', '%' . $request->search . '%'); + } + + // Filtre par statut + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + // Filtre par date + if ($request->filled('date_from')) { + $query->whereDate('created_at', '>=', $request->date_from); + } + if ($request->filled('date_to')) { + $query->whereDate('created_at', '<=', $request->date_to); + } + + $orders = $query->latest()->paginate(10); + + return view('layouts.boutique.orders.index', compact('orders')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index cb6af97..dcb3626 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -7,48 +7,180 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; - - - class ProductController extends Controller { + /** + * Admin - Liste des produits + */ public function index() { - $produits = Product::with('category')->paginate(10); // ✅ Paginer les produits - return view('admin.layout.boutique.list_produit', compact('produits')); + $produits = Product::with('category')->paginate(10); + $categories = Category::all(); + return view('admin.layout.boutique.list_produit', compact('produits', 'categories')); } - + /** + * Admin - Formulaire de création + */ public function create() { $categories = Category::all(); return view('admin.layout.boutique.add_product', compact('categories')); } + /** + * Admin - Enregistrement d'un nouveau produit + */ public function store(Request $request) { $request->validate([ 'nom' => 'required|string|max:255', 'description' => 'required', - 'prix' => 'required|numeric', - 'qte' => 'required|integer', - 'statut_stock' => 'required', - 'path_img' => 'image|mimes:jpeg,png,jpg,gif|max:2048', + 'prix' => 'required|numeric|min:0', + 'qte' => 'required|integer|min:0', + 'path_img' => 'nullable|file|mimes:jpeg,png,jpg,gif,svg,webp|max:2048', 'category_id' => 'required|exists:categories,id', ]); - $imagePath = $request->file('path_img') ? $request->file('path_img')->store('produits', 'public') : null; + $imagePath = null; + if ($request->hasFile('path_img')) { + $imagePath = $request->file('path_img')->store('produits', 'public'); + } + + // Déterminer automatiquement le statut du stock + $statusStock = $this->determineStockStatus($request->qte); Product::create([ 'nom' => $request->nom, 'description' => $request->description, 'prix' => $request->prix, 'qte' => $request->qte, - 'statut_stock' => $request->statut_stock, + 'status_stock' => $statusStock, 'path_img' => $imagePath, 'category_id' => $request->category_id, ]); - return redirect()->route('admin.produits.index')->with('success', 'Produit ajouté avec succès'); + return redirect()->route('products.index')->with('success', 'Produit ajouté avec succès'); + } + + /** + * Admin - Formulaire de modification + */ + public function edit(Product $product) + { + $categories = Category::all(); + return view('admin.layout.boutique.edit_product', compact('product', 'categories')); + } + + /** + * Admin - Mise à jour d'un produit + */ + public function update(Request $request, Product $product) + { + $request->validate([ + 'nom' => 'required|string|max:255', + 'description' => 'required', + 'prix' => 'required|numeric|min:0', + 'qte' => 'required|integer|min:0', + 'path_img' => 'nullable|file|mimes:jpeg,png,jpg,gif,svg,webp|max:2048', + 'category_id' => 'required|exists:categories,id', + ]); + + // Gestion de l'image + if ($request->hasFile('path_img')) { + // Supprimer l'ancienne image + if ($product->path_img && Storage::disk('public')->exists($product->path_img)) { + Storage::disk('public')->delete($product->path_img); + } + $product->path_img = $request->file('path_img')->store('produits', 'public'); + } + + // Déterminer le statut du stock + $statusStock = $this->determineStockStatus($request->qte); + + // Mise à jour + $product->update([ + 'nom' => $request->nom, + 'description' => $request->description, + 'prix' => $request->prix, + 'qte' => $request->qte, + 'status_stock' => $statusStock, + 'category_id' => $request->category_id, + 'path_img' => $product->path_img, + ]); + + return redirect()->route('products.index')->with('success', 'Produit mis à jour avec succès'); + } + + /** + * Admin - Suppression d'un produit + */ + public function destroy(Product $product) + { + // Supprimer l'image + if ($product->path_img && Storage::disk('public')->exists($product->path_img)) { + Storage::disk('public')->delete($product->path_img); + } + + $product->delete(); + + return redirect()->route('products.index')->with('success', 'Produit supprimé avec succès'); + } + + /** + * CLIENT - Liste des produits (Boutique) + */ + public function shop(Request $request) + { + $categories = Category::all(); + + // Filtrer les produits en stock + $query = Product::with('category')->where('qte', '>', 0); + + // Filtre par catégorie + if ($request->filled('category_id')) { + $query->where('category_id', $request->category_id); + } + + // Tri + if ($request->filled('sort')) { + switch ($request->sort) { + case 'price_asc': + $query->orderBy('prix', 'asc'); + break; + case 'price_desc': + $query->orderBy('prix', 'desc'); + break; + case 'newest': + $query->latest(); + break; + } + } + + $produits = $query->paginate(12); + + return view('layouts.boutique.product-list', compact('produits', 'categories')); + } + + /** + * CLIENT - Détails d'un produit + */ + public function voir(Product $product) + { + return view('layouts.boutique.product-detail', compact('product')); + } + + /** + * Déterminer le statut du stock automatiquement + */ + private function determineStockStatus($quantity) + { + if ($quantity == 0) { + return 'out_of_stock'; + } elseif ($quantity <= 5) { + return 'low_stock'; + } else { + return 'in_stock'; + } } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/PublicationController.php b/app/Http/Controllers/PublicationController.php index 1ec1f68..9f81405 100644 --- a/app/Http/Controllers/PublicationController.php +++ b/app/Http/Controllers/PublicationController.php @@ -9,6 +9,53 @@ class PublicationController extends Controller { + // Afficher la liste des publications et catégories + public function index1(Request $request) + { + // Récupération de toutes les catégories avec le nombre de publications + $categories = PubCategory::withCount('publications')->get(); + + // Récupération des publications, triées par date décroissante + $publications = Publication::with('category') + ->where('status', 'publish') // optionnel : si tu as un statut + ->orderBy('created_at', 'desc') + ->paginate(6); // pagination 6 articles par page + + // Si filtrage par catégorie (optionnel) + /* if ($request->has('category')) { + $publication = Publication::with('category') + ->whereHas('category', function ($query) use ($request) { + $query->where('id', $request->category); + }) + ->orderBy('created_at', 'desc') + ->paginate(6); + }*/ + + return view('layouts.blog.blog-list', compact('publications', 'categories')); + } + public function show($id) + { + $article = Publication::with('category')->findOrFail($id); + return view('layouts.blog.blog-article', compact('article')); + } + + // Afficher la liste des catégories + public function categories() + { + $categories = PubCategory::withCount('publications')->get(); + return view('layouts.blog.blog-category', compact('categories')); + } + + // Afficher les articles d'une catégorie + public function articles($id) + { + $category = PubCategory::findOrFail($id); + $articles = Publication::where('pub_category_id', $id)->where('status', 'publish')->latest()->get(); + return view('layouts.blog.blog-articles', compact('category', 'articles')); + } + + // Afficher un article en particulier + public function index() { $publications = Publication::with('category')->latest()->paginate(10); diff --git a/app/Http/Controllers/QuizzController.php b/app/Http/Controllers/QuizzController.php index d05f500..c454e53 100644 --- a/app/Http/Controllers/QuizzController.php +++ b/app/Http/Controllers/QuizzController.php @@ -5,12 +5,11 @@ use App\Models\Module; use App\Models\Question; use App\Models\Quizz; +use App\Models\user_formation; use App\Models\User_Quizz; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; -use Carbon\Carbon; - class QuizzController extends Controller { @@ -55,9 +54,8 @@ public function storeOrUpdate(Request $request, Module $module) return redirect()->route('quizz.manage', $module->id) ->with('success', 'Quiz créé avec succès.'); - } catch (\Exception $e) { - return redirect()->back()->with('error', 'Erreur lors de la création: '.$e->getMessage()); + return redirect()->back()->with('error', 'Erreur lors de la création: ' . $e->getMessage()); } } @@ -107,7 +105,7 @@ private function handleAjaxStoreOrUpdate(Request $request, Module $module) // S'assurer que le quiz existe $quizz = $module->quizz ?? Quizz::create([ 'module_id' => $module->id, - 'titre' => 'Quizz du module '.$module->titre, + 'titre' => 'Quizz du module ' . $module->titre, ]); // Créer la question @@ -147,20 +145,18 @@ private function handleAjaxStoreOrUpdate(Request $request, Module $module) 'success' => false, 'message' => 'Données manquantes', ], 400); - } catch (\Illuminate\Validation\ValidationException $e) { return response()->json([ 'success' => false, 'message' => 'Erreur de validation', 'errors' => $e->errors(), ], 422); - } catch (\Exception $e) { - Log::error('Erreur AJAX: '.$e->getMessage()); + Log::error('Erreur AJAX: ' . $e->getMessage()); return response()->json([ 'success' => false, - 'message' => 'Erreur interne du serveur: '.$e->getMessage(), + 'message' => 'Erreur interne du serveur: ' . $e->getMessage(), ], 500); } } @@ -215,7 +211,6 @@ public function updateQuestion(Request $request, $questionId) 'reponses' => $updatedReponses, 'correct_reponses' => $correctReponses, ], 200); - } catch (\Illuminate\Validation\ValidationException $e) { return response()->json([ 'success' => false, @@ -223,11 +218,11 @@ public function updateQuestion(Request $request, $questionId) 'errors' => $e->errors(), ], 422); } catch (\Exception $e) { - Log::error('Erreur lors de la modification de question: '.$e->getMessage()); + Log::error('Erreur lors de la modification de question: ' . $e->getMessage()); return response()->json([ 'success' => false, - 'message' => 'Erreur interne: '.$e->getMessage(), + 'message' => 'Erreur interne: ' . $e->getMessage(), ], 500); } } @@ -248,13 +243,12 @@ public function deleteQuestion($questionId) 'success' => true, 'message' => 'Question supprimée avec succès', ], 200); - } catch (\Exception $e) { - Log::error('Erreur lors de la suppression de question: '.$e->getMessage()); + Log::error('Erreur lors de la suppression de question: ' . $e->getMessage()); return response()->json([ 'success' => false, - 'message' => 'Erreur lors de la suppression: '.$e->getMessage(), + 'message' => 'Erreur lors de la suppression: ' . $e->getMessage(), ], 500); } } @@ -291,7 +285,6 @@ public function updateTitle(Request $request, Quizz $quizz) return redirect()->route('quizz.manage', $quizz->module_id) ->with('success', 'Titre du quizz modifié avec succès.'); - } catch (\Illuminate\Validation\ValidationException $e) { if ($request->ajax() || $request->wantsJson()) { return response()->json([ @@ -304,19 +297,18 @@ public function updateTitle(Request $request, Quizz $quizz) return redirect()->back() ->withErrors($e->errors()) ->withInput(); - } catch (\Exception $e) { - Log::error('Erreur lors de la modification du titre du quizz: '.$e->getMessage()); + Log::error('Erreur lors de la modification du titre du quizz: ' . $e->getMessage()); if ($request->ajax() || $request->wantsJson()) { return response()->json([ 'success' => false, - 'message' => 'Erreur interne: '.$e->getMessage(), + 'message' => 'Erreur interne: ' . $e->getMessage(), ], 500); } return redirect()->back() - ->with('error', 'Erreur lors de la modification: '.$e->getMessage()); + ->with('error', 'Erreur lors de la modification: ' . $e->getMessage()); } } @@ -350,81 +342,128 @@ public function destroy(Request $request, Quizz $quizz) return redirect()->route('quizz.manage', $moduleId) ->with('success', 'Quizz supprimé avec succès.'); - } catch (\Exception $e) { - Log::error('Erreur lors de la suppression du quizz: '.$e->getMessage()); + Log::error('Erreur lors de la suppression du quizz: ' . $e->getMessage()); if ($request->ajax() || $request->wantsJson()) { return response()->json([ 'success' => false, - 'message' => 'Erreur lors de la suppression: '.$e->getMessage(), + 'message' => 'Erreur lors de la suppression: ' . $e->getMessage(), ], 500); } return redirect()->back() - ->with('error', 'Erreur lors de la suppression: '.$e->getMessage()); + ->with('error', 'Erreur lors de la suppression: ' . $e->getMessage()); } } // Mes routes a moi - public function show($quizzId) - { - $quizz = Quizz::with('questions.reponses')->findOrFail($quizzId); - - $userQuizz = User_Quizz::where('user_id', Auth::id()) - ->where('quizz_id', $quizzId) - ->first(); - - $canResubmit = true; - - if ($userQuizz && $userQuizz->updated_at) { - $canResubmit = Carbon::now()->diffInHours($userQuizz->updated_at) >= 24; +public function show($quizzId) +{ + $quizz = Quizz::with('questions.reponses', 'module.formation')->findOrFail($quizzId); + $userId = Auth::id(); + + $userQuizz = User_Quizz::where('user_id', $userId) + ->where('quizz_id', $quizzId) + ->first(); + + $canResubmit = true; + $canViewAnswers = false; + + if ($userQuizz && $userQuizz->updated_at) { + if ($userQuizz->score >= 70) { + $canResubmit = false; + $canViewAnswers = true; + } else { + $hoursSinceLastAttempt = now()->diffInHours($userQuizz->updated_at); + if ($hoursSinceLastAttempt < 24) { + $canResubmit = false; + } } - - return view('quizz.show', compact('quizz', 'userQuizz', 'canResubmit')); } - public function submit(Request $request, $quizzId) - { - $quizz = Quizz::with('questions.reponses')->findOrFail($quizzId); + return view('quizz.show', compact('quizz', 'userQuizz', 'canResubmit', 'canViewAnswers')); +} - $userQuizz = User_Quizz::where('user_id', Auth::id()) - ->where('quizz_id', $quizzId) - ->first(); - // Vérifie que l'utilisateur n'a pas soumis le quiz il y a moins de 24h - if ($userQuizz && $userQuizz->updated_at && now()->diffInHours($userQuizz->updated_at) < 24) { + public function submit(Request $request, $quizzId) +{ + $userId = Auth::id(); + $quizz = Quizz::with('questions.reponses', 'module.formation')->findOrFail($quizzId); + + $userQuizz = User_Quizz::where('user_id', $userId) + ->where('quizz_id', $quizzId) + ->first(); + + if ($userQuizz && $userQuizz->updated_at) { + if ($userQuizz->score < 70 && now()->diffInHours($userQuizz->updated_at) < 24) { return redirect()->back()->with('error', 'Vous devez attendre 24 heures avant de pouvoir refaire ce quiz.'); } + } - $score = 0; - $totalQuestions = $quizz->questions->count(); + $score = 0; + $totalQuestions = $quizz->questions->count(); + $userAnswers = []; - foreach ($quizz->questions as $question) { - $userAnswer = $request->input('question_'.$question->id); - $correctAnswer = $question->reponses->where('is_correct', true)->first(); + foreach ($quizz->questions as $question) { + $correctAnswers = $question->reponses->where('is_correct', true)->pluck('id')->sort()->values(); + $userSelected = collect($request->input('question_'.$question->id, []))->map(fn($val) => (int) $val)->sort()->values(); - if ($correctAnswer && $userAnswer == $correctAnswer->id) { - $score++; - } - } + $userAnswers[$question->id] = $userSelected; - $finalScore = $totalQuestions > 0 ? ($score / $totalQuestions) * 100 : 0; + if ($userSelected == $correctAnswers) { + $score++; + } + } - User_Quizz::updateOrCreate( - [ - 'user_id' => Auth::id(), - 'quizz_id' => $quizzId, - ], - [ - 'score' => $finalScore, - ] - ); + $finalScore = $totalQuestions > 0 ? ($score / $totalQuestions) * 100 : 0; + $termine = ($finalScore == 100) ? 1 : 0; - return redirect()->back()->with([ - 'success' => 'Quiz terminé ! Votre score : '.round($finalScore).'%', + User_Quizz::updateOrCreate( + [ + 'user_id' => $userId, + 'quizz_id' => $quizzId, + ], + [ 'score' => $finalScore, - ]); - } + 'termine' => $termine, + 'reponses' => json_encode($userAnswers), + 'updated_at' => now(), + ] + ); + + // Mise à jour de la progression + $formationId = $quizz->module->formation->id; + + $quizzIds = Quizz::whereHas('module', function ($query) use ($formationId) { + $query->where('formation_id', $formationId); + })->pluck('id'); + + $userQuizzScores = User_Quizz::where('user_id', $userId) + ->whereIn('quizz_id', $quizzIds) + ->pluck('score'); + + $allPerfect = $userQuizzScores->count() === $quizzIds->count() && $userQuizzScores->every(fn($score) => $score >= 70); + $progression = $allPerfect ? 100 : ($userQuizzScores->avg() ?? 0); + $status = $allPerfect ? 'terminé' : 'en_attente'; + + user_formation::updateOrCreate( + [ + 'user_id' => $userId, + 'formation_id' => $formationId, + ], + [ + 'progression' => $progression, + 'status' => $status, + 'updated_at' => now(), + ] + ); + + return redirect()->back()->with([ + 'success' => 'Quiz terminé ! Votre score : '.round($finalScore).'%', + 'score' => round($finalScore), + ]); +} + } diff --git a/app/Http/Controllers/StudentController.php b/app/Http/Controllers/StudentController.php index dd4dc5d..519776f 100644 --- a/app/Http/Controllers/StudentController.php +++ b/app/Http/Controllers/StudentController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\Formation; use App\Models\Paiement; use App\Models\user_formation; use App\Models\UserLesson; @@ -99,30 +100,28 @@ public function index(Request $request) } public function ShowTranings(Request $request) { + $userId = Auth::id(); - - $user = Auth::user(); - - $formations = $user->formationsAchetees() - ->with('modules.lessons') // pour calcul durée ou leçons + // Récupère directement les user_formations avec les formations et leurs relations + $userFormations = user_formation::with(['formation.modules.lessons']) + ->where('user_id', $userId) ->paginate(6); - // Si requête AJAX, on ne renvoie que le contenu de la section + // Si c'est une requête AJAX if ($request->ajax()) { - return view('space-etudiant.layout.list-formation-section', compact('formations')); + return view('space-etudiant.layout.list-formation-section', compact('userFormations')); } - // Sinon, page complète - return view('space-etudiant.layout.list-formation-cours', compact('formations')); + // Sinon on renvoie la page complète + return view('space-etudiant.layout.list-formation-cours', compact('userFormations')); } - public function Showcertfication(Request $request) { $user = Auth::user(); $certificats = $user->formationsAchetees() ->wherePivotNotNull('path_attestation') - ->select('formations.id', 'formations.titre', 'formations.image', 'formations.created_at') + ->select('formations.id', 'formations.titre', 'formations.image_path', 'formations.created_at') ->withPivot('path_attestation', 'updated_at') ->orderByDesc('user_formations.updated_at') ->paginate(6); @@ -152,7 +151,8 @@ public function ShowFacturations(Request $request) return view('space-etudiant.layout.list-paiements', compact('paiements')); } - public function Showhelp(Request $request){ + public function Showhelp(Request $request) + { if ($request->ajax()) { return view('space-etudiant.layout.help-section'); // section partielle @@ -163,7 +163,7 @@ public function Showhelp(Request $request){ public function sendMail(Request $request) { - $request->validate([ + $request->validate([ 'message' => 'required|string|min:5', ]); @@ -175,7 +175,8 @@ public function sendMail(Request $request) return response()->json(['success' => 'Votre message a été envoyé avec succès ✅']); } - public function ShowSettings(Request $request){ + public function ShowSettings(Request $request) + { $user = Auth::user(); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index b3ff58b..0b0d106 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -12,51 +12,61 @@ class UserController extends Controller { // - public function index() - { - $user = Auth::user(); + public function index() +{ + $user = Auth::user(); - if ($user->role !== "admin") { - return redirect()->route("accueil"); + // Si ce n'est pas un admin + if ($user->role !== 'admin') { + // Vérifie d'abord l'email + if (!$user->hasVerifiedEmail()) { + return redirect()->route('verification.notice') + ->with('error', 'Veuillez vérifier votre adresse e-mail avant d’accéder au tableau de bord.'); } - // 📊 Récupération dynamique des données - $nbProduits = Product::count(); - $nbFormations = Formation::count(); - $nbQuiz = Quizz::count(); - $totalPaiements = Paiement::sum('montant_payé'); // Assure-toi que la colonne montant existe - - $cards = [ - [ - 'title' => 'Produits', - 'value' => $nbProduits, - 'percent' => '+10%', // tu peux aussi le calculer dynamiquement si tu veux - 'percentColor' => 'col-green', - 'img' => 'assets/img/banner/shop.png', - ], - [ - 'title' => 'Formations', - 'value' => $nbFormations, - 'percent' => '-5%', - 'percentColor' => 'col-orange', - 'img' => 'assets/img/banner/formations.png', - ], - [ - 'title' => 'Quiz', - 'value' => $nbQuiz, - 'percent' => '+12%', - 'percentColor' => 'col-green', - 'img' => 'assets/img/banner/quizz.png', - ], - [ - 'title' => 'Paiements', - 'value' => number_format($totalPaiements, 0, ',', ' ') . ' CFA', - 'percent' => '+42%', - 'percentColor' => 'col-green', - 'img' => 'assets/img/banner/paiements.png', - ], - ]; - - return view('admin.layout.index', compact('cards')); + // Redirige les non-admins + return redirect()->route('accueil'); } + + // Si on arrive ici, c’est un admin + $nbProduits = Product::count(); + $nbFormations = Formation::count(); + $nbQuiz = Quizz::count(); + $totalPaiements = Paiement::sum('montant_payé'); + + $cards = [ + [ + 'title' => 'Produits', + 'value' => $nbProduits, + 'percent' => '+10%', + 'percentColor' => 'col-green', + 'img' => 'assets/img/banner/shop.png', + ], + [ + 'title' => 'Formations', + 'value' => $nbFormations, + 'percent' => '-5%', + 'percentColor' => 'col-orange', + 'img' => 'assets/img/banner/formations.png', + ], + [ + 'title' => 'Quiz', + 'value' => $nbQuiz, + 'percent' => '+12%', + 'percentColor' => 'col-green', + 'img' => 'assets/img/banner/quizz.png', + ], + [ + 'title' => 'Paiements', + 'value' => number_format($totalPaiements, 0, ',', ' ') . ' CFA', + 'percent' => '+42%', + 'percentColor' => 'col-green', + 'img' => 'assets/img/banner/paiements.png', + ], + ]; + + return view('admin.layout.index', compact('cards')); +} + + } diff --git a/app/Http/Middleware/EnsureUserIsClient.php b/app/Http/Middleware/EnsureUserIsClient.php new file mode 100644 index 0000000..bbc3011 --- /dev/null +++ b/app/Http/Middleware/EnsureUserIsClient.php @@ -0,0 +1,28 @@ +role !== 'client') { + abort(403, 'Accès interdit. Réservé aux clients.'); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/UpdateLessonRequest.php b/app/Http/Requests/UpdateLessonRequest.php index 6995861..89fdeea 100644 --- a/app/Http/Requests/UpdateLessonRequest.php +++ b/app/Http/Requests/UpdateLessonRequest.php @@ -19,7 +19,7 @@ public function authorize(): bool * * @return array|string> */ - public function rules(): array + public function rules(): array { return [ 'titre' => 'required|string|max:255', @@ -34,10 +34,10 @@ public function rules(): array public function messages(): array { return [ - 'video_file.mimes' => 'Le fichier vidéo doit être de type: mp4, avi, mov, wmv', - 'video_file.max' => 'La vidéo ne doit pas dépasser 100MB', - 'pdf_file.mimes' => 'Le fichier PDF doit être au format PDF', - 'pdf_file.max' => 'Le PDF ne doit pas dépasser 50MB', + 'video_url.mimes' => 'Le fichier vidéo doit être de type: mp4, avi, mov, wmv', + 'video_url.max' => 'La vidéo ne doit pas dépasser 100MB', + 'pdf_url.mimes' => 'Le fichier PDF doit être au format PDF', + 'pdf_url.max' => 'Le PDF ne doit pas dépasser 50MB', ]; } } \ No newline at end of file diff --git a/app/Http/View/Composers/CartComposer.php b/app/Http/View/Composers/CartComposer.php new file mode 100644 index 0000000..7a11b90 --- /dev/null +++ b/app/Http/View/Composers/CartComposer.php @@ -0,0 +1,25 @@ +first(); + $cartCount = $cart ? $cart->totalItems() : 0; + } + + $view->with('cartCount', $cartCount); + } +} \ No newline at end of file diff --git a/app/Mail/OrderStatusChanged.php b/app/Mail/OrderStatusChanged.php new file mode 100644 index 0000000..9705129 --- /dev/null +++ b/app/Mail/OrderStatusChanged.php @@ -0,0 +1,30 @@ +order = $order; + $this->oldStatus = $oldStatus; + $this->newStatus = $newStatus; + } + + public function build() + { + return $this->subject('Mise à jour de votre commande ' . $this->order->order_code) + ->view('emails.order-status-changed'); + } +} \ No newline at end of file diff --git a/app/Models/Cart.php b/app/Models/Cart.php index 697cac9..e84cf26 100644 --- a/app/Models/Cart.php +++ b/app/Models/Cart.php @@ -24,4 +24,30 @@ public function products() { return $this->belongsToMany(Product::class, 'cart_items')->withPivot('qte'); } + + // Calculer le total du panier + public function calculateTotal() + { + $total = 0; + foreach ($this->items as $item) { + $total += $item->product->prix * $item->qte; + } + $this->total_price = $total; + $this->save(); + return $total; + } + + // Vider le panier + public function clear() + { + $this->items()->delete(); + $this->total_price = 0; + $this->save(); + } + + // Compter les articles + public function totalItems() + { + return $this->items->sum('qte'); + } } diff --git a/app/Models/Cart_Item.php b/app/Models/CartItem.php similarity index 100% rename from app/Models/Cart_Item.php rename to app/Models/CartItem.php diff --git a/app/Models/Formation.php b/app/Models/Formation.php index 7a308b9..9b91e35 100644 --- a/app/Models/Formation.php +++ b/app/Models/Formation.php @@ -66,6 +66,7 @@ public function totalAvis() public function users() { return $this->belongsToMany(User::class, 'user_formations') + ->withPivot('progression') ->withTimestamps(); } @@ -84,4 +85,6 @@ public function getTotalLessonsAttribute() { return $this->lessons()->count(); } + + // Dans le modèle Formation } diff --git a/app/Models/Module.php b/app/Models/Module.php index 4a83d0d..69017b3 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -5,7 +5,6 @@ use Illuminate\Database\Eloquent\Model; class Module extends Model - { protected $fillable = ['titre', 'formation_id', 'ordre']; @@ -15,7 +14,6 @@ public function formation() return $this->belongsTo(Formation::class); } - public function lessons() { return $this->hasMany(Lesson::class); diff --git a/app/Models/Order.php b/app/Models/Order.php index 0bd4f82..cae296f 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -1,4 +1,5 @@ order_code = self::generateOrderCode(); + }); + } + + /** + * Générer un code de commande unique + */ + public static function generateOrderCode() + { + $year = date('Y'); + $lastOrder = self::whereYear('created_at', $year) + ->orderBy('id', 'desc') + ->first(); + + $number = $lastOrder ? intval(substr($lastOrder->order_code, -3)) + 1 : 1; + + return 'CMD-' . $year . '-' . str_pad($number, 3, '0', STR_PAD_LEFT); + } + + /** + * Relation : une commande appartient à un utilisateur + */ public function user() { return $this->belongsTo(User::class); } + /** + * Relation : une commande contient plusieurs produits + */ public function products() { - return $this->belongsToMany(Product::class, 'order_products')->withPivot('qte_commander'); + return $this->belongsToMany(Product::class, 'order_products') + ->withPivot('qte_commander') + ->withTimestamps(); + } + + /** + * Relation : une commande peut avoir un paiement + */ + public function paiement() + { + return $this->hasOne(Paiement::class); + } + + /** + * Obtenir le statut en français + */ + public function getStatusLabelAttribute() + { + return match($this->status) { + 'pending' => 'En attente', + 'confirmed' => 'Confirmée', + 'processing' => 'En préparation', + 'shipped' => 'Expédiée', + 'delivered' => 'Livrée', + 'cancelled' => 'Annulée', + default => $this->status + }; + } + + /** + * Obtenir la classe CSS du badge selon le statut + */ + public function getStatusBadgeClassAttribute() + { + return match($this->status) { + 'pending' => 'bg-warning text-dark', + 'confirmed' => 'bg-info', + 'processing' => 'bg-primary', + 'shipped' => 'bg-secondary', + 'delivered' => 'bg-success', + 'cancelled' => 'bg-danger', + default => 'bg-secondary' + }; + } + + /** + * Vérifier si la commande peut être annulée + */ + public function canBeCancelled() + { + return in_array($this->status, ['pending', 'confirmed']); + } + + /** + * Scopes pour filtrer par statut + */ + public function scopePending($query) + { + return $query->where('status', 'pending'); + } + + public function scopeConfirmed($query) + { + return $query->where('status', 'confirmed'); + } + + public function scopeProcessing($query) + { + return $query->where('status', 'processing'); + } + + public function scopeShipped($query) + { + return $query->where('status', 'shipped'); + } + + public function scopeDelivered($query) + { + return $query->where('status', 'delivered'); } -} +} \ No newline at end of file diff --git a/app/Models/Order_Product.php b/app/Models/OrderProduct.php similarity index 100% rename from app/Models/Order_Product.php rename to app/Models/OrderProduct.php diff --git a/app/Models/Paiement.php b/app/Models/Paiement.php index 1f5dd0c..f7dee25 100644 --- a/app/Models/Paiement.php +++ b/app/Models/Paiement.php @@ -13,7 +13,8 @@ class Paiement extends Model 'montant_payé', 'moyen_de_paiment', 'status', - 'transaction_id' + 'transaction_id', + 'order_id' ]; public function formation() @@ -25,4 +26,8 @@ public function user() { return $this->belongsTo(\App\Models\User::class); } + public function order() +{ + return $this->belongsTo(Order::class); +} } diff --git a/app/Models/Product.php b/app/Models/Product.php index 2735c20..b4c06a7 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -14,6 +14,7 @@ class Product extends Model 'status_stock', 'path_img', 'category_id' ]; + // Relations public function category() { return $this->belongsTo(Category::class); @@ -28,4 +29,51 @@ public function orders() { return $this->belongsToMany(Order::class, 'order_products')->withPivot('qte_commander'); } -} + + // ✅ AJOUTER CES MÉTHODES + + /** + * Vérifier si le produit est en stock + */ + public function isInStock($quantity = 1) + { + return $this->qte >= $quantity; + } + + /** + * Réduire le stock + */ + public function decrementStock($quantity) + { + if ($this->isInStock($quantity)) { + $this->decrement('qte', $quantity); + $this->updateStockStatus(); + return true; + } + return false; + } + + /** + * Augmenter le stock (en cas d'annulation) + */ + public function incrementStock($quantity) + { + $this->increment('qte', $quantity); + $this->updateStockStatus(); + } + + /** + * Mettre à jour le statut du stock automatiquement + */ + public function updateStockStatus() + { + if ($this->qte == 0) { + $this->status_stock = 'out_of_stock'; + } elseif ($this->qte <= 5) { + $this->status_stock = 'low_stock'; + } else { + $this->status_stock = 'in_stock'; + } + $this->save(); + } +} \ No newline at end of file diff --git a/app/Models/Quizz.php b/app/Models/Quizz.php index f472941..37a4668 100644 --- a/app/Models/Quizz.php +++ b/app/Models/Quizz.php @@ -2,9 +2,6 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; - namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -15,6 +12,7 @@ class Quizz extends Model use HasFactory; protected $table = 'quizzs'; + protected $fillable = ['titre', 'module_id']; // Un quizz appartient à un module @@ -29,10 +27,12 @@ public function questions() return $this->hasMany(Question::class, 'quizz_id'); } - public function users() + public function users() { return $this->belongsToMany(User::class, 'user__quizzs') - ->withPivot('score') - ->withTimestamps(); + ->withPivot('score') + ->withTimestamps(); } + + } diff --git a/app/Models/User.php b/app/Models/User.php index 4321c21..fa5f68c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -89,4 +89,14 @@ public function paiements() { return $this->hasMany(Paiement::class); } + + /** + * Relation : un utilisateur peut avoir plusieurs commandes + */ + public function orders() + { + return $this->hasMany(Order::class); + } + + } diff --git a/app/Models/User_Quizz.php b/app/Models/User_Quizz.php index 98e18a6..344512b 100644 --- a/app/Models/User_Quizz.php +++ b/app/Models/User_Quizz.php @@ -11,6 +11,8 @@ class User_Quizz extends Model 'user_id', 'quizz_id', 'score', + 'termine', + 'reponses', ]; // Relation avec l'utilisateur (User) diff --git a/app/Models/user_formation.php b/app/Models/user_formation.php index 2d2039a..07606be 100644 --- a/app/Models/user_formation.php +++ b/app/Models/user_formation.php @@ -7,14 +7,16 @@ class user_formation extends Model { // + protected $table = 'user_formations'; protected $fillable = [ 'user_id', 'formation_id', 'progression', - 'path_attestation' + 'path_attestation', + 'status' ]; - + // Relation vers Formation public function formation() { @@ -27,5 +29,5 @@ public function user() return $this->belongsTo(User::class, 'user_id'); } - + } diff --git a/app/Notifications/CustomResetPassword.php b/app/Notifications/CustomResetPassword.php new file mode 100644 index 0000000..2e3055c --- /dev/null +++ b/app/Notifications/CustomResetPassword.php @@ -0,0 +1,41 @@ +token = $token; + } + + public function via($notifiable) + { + return ['mail']; + } + + public function toMail($notifiable) + { + $url = url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + return (new MailMessage) + ->view('emails.reset-password', [ + 'url' => $url, + 'user' => $notifiable + ]) + ->subject('Réinitialisation de votre mot de passe'); + } +} + diff --git a/app/Notifications/CustomVerifyEmail.php b/app/Notifications/CustomVerifyEmail.php new file mode 100644 index 0000000..24ccc66 --- /dev/null +++ b/app/Notifications/CustomVerifyEmail.php @@ -0,0 +1,69 @@ + + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + // Génère le lien de vérification signé + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + Carbon::now()->addMinutes(60), + [ + 'id' => $notifiable->getKey(), + 'hash' => sha1($notifiable->getEmailForVerification()), + ] + ); + + // Retourne la vue Blade personnalisée + return (new MailMessage) + ->subject('Vérifiez votre adresse e-mail') + ->view('emails.verify-email', [ + 'user' => $notifiable, + 'url' => $verificationUrl, + ]); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + // + ]; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 2b85139..c47999c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,7 +9,8 @@ use Illuminate\Support\Facades\View; use App\Http\View\Composers\MasterComposer; // use Illuminate\Pagination\Paginator; - + +use App\Models\Cart; class AppServiceProvider extends ServiceProvider { /** @@ -29,6 +30,11 @@ public function boot(): void // Paginator::useBootstrapFive(); // View::composer('space-etudiant.master', MasterComposer::class); + + // Partager le compteur du panier avec toutes les vues + View::composer('*', \App\Http\View\Composers\CartComposer::class); + + } diff --git a/composer.json b/composer.json index becd983..5992987 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,10 @@ "license": "MIT", "require": { "php": "^8.2", + "ext-bcmath": "*", + "ext-gd": "*", + "ext-pdo_mysql": "*", + "barryvdh/laravel-dompdf": "^3.1", "fedapay/fedapay-php": "^0.4.7", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", diff --git a/composer.lock b/composer.lock index 35b3756..123f7da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c27f3caf7844efef67b646d976be85d5", + "content-hash": "870a916244398b927b3bf785f2aa32b8", "packages": [ + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9|^10", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-02-13T15:07:54+00:00" + }, { "name": "brick/math", "version": "0.14.0", @@ -377,31 +454,185 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.4", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + }, + "time": "2025-10-29T12:43:30+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.0" + "php": "^8.2|^8.3|^8.4|^8.5" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", "extra": { @@ -432,7 +663,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" }, "funding": [ { @@ -440,7 +671,7 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2025-10-31T18:51:33+00:00" }, { "name": "egulias/email-validator", @@ -1155,16 +1386,16 @@ }, { "name": "laravel/framework", - "version": "v12.28.1", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "868c1f2d3dba4df6d21e3a8d818479f094cfd942" + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/868c1f2d3dba4df6d21e3a8d818479f094cfd942", - "reference": "868c1f2d3dba4df6d21e3a8d818479f094cfd942", + "url": "https://api.github.com/repos/laravel/framework/zipball/3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", "shasum": "" }, "require": { @@ -1276,7 +1507,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.6.5", + "orchestra/testbench-core": "^10.7.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -1301,7 +1532,7 @@ "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", @@ -1370,20 +1601,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-09-04T14:58:12+00:00" + "time": "2025-11-04T15:39:33+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.6", + "version": "v0.3.7", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "86a8b692e8661d0fb308cec64f3d176821323077" + "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", - "reference": "86a8b692e8661d0fb308cec64f3d176821323077", + "url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc", + "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc", "shasum": "" }, "require": { @@ -1400,8 +1631,8 @@ "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-mockery": "^1.1" + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" }, "suggest": { "ext-pcntl": "Required for the spinner to be animated." @@ -1427,22 +1658,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.6" + "source": "https://github.com/laravel/prompts/tree/v0.3.7" }, - "time": "2025-07-07T14:17:42+00:00" + "time": "2025-09-19T13:47:56+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.4", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + "reference": "038ce42edee619599a1debb7e81d7b3759492819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", + "reference": "038ce42edee619599a1debb7e81d7b3759492819", "shasum": "" }, "require": { @@ -1490,7 +1721,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-03-19T13:51:03+00:00" + "time": "2025-10-09T13:42:30+00:00" }, { "name": "laravel/tinker", @@ -1749,16 +1980,16 @@ }, { "name": "league/flysystem", - "version": "3.30.0", + "version": "3.30.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" + "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c139fd65c1f796b926f4aec0df37f6caa959a8da", + "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da", "shasum": "" }, "require": { @@ -1826,9 +2057,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.1" }, - "time": "2025-06-25T13:29:59+00:00" + "time": "2025-10-20T15:35:26+00:00" }, { "name": "league/flysystem-local", @@ -2109,6 +2340,73 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "monolog/monolog", "version": "3.9.0", @@ -2319,25 +2617,25 @@ }, { "name": "nette/schema", - "version": "v1.3.2", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.4" + "php": "8.1 - 8.5" }, "require-dev": { "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -2347,6 +2645,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -2375,9 +2676,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.2" + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2024-10-06T23:10:23+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { "name": "nette/utils", @@ -2470,16 +2771,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -2522,37 +2823,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.3.1", + "version": "v2.3.2", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" + "reference": "eb61920a53057a7debd718a5b89c2178032b52c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0", + "reference": "eb61920a53057a7debd718a5b89c2178032b52c0", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.2.6" + "symfony/console": "^7.3.4" }, "require-dev": { - "illuminate/console": "^11.44.7", - "laravel/pint": "^1.22.0", + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.2", - "phpstan/phpstan": "^1.12.25", + "pestphp/pest": "^2.36.0 || ^3.8.4", + "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.2.6", + "symfony/var-dumper": "^7.3.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -2595,7 +2896,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2" }, "funding": [ { @@ -2611,7 +2912,7 @@ "type": "github" } ], - "time": "2025-05-08T08:14:37+00:00" + "time": "2025-10-18T11:10:27+00:00" }, { "name": "php-ffmpeg/php-ffmpeg", @@ -3240,16 +3541,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.10", + "version": "v0.12.14", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" + "reference": "95c29b3756a23855a30566b745d218bee690bef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", - "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/95c29b3756a23855a30566b745d218bee690bef2", + "reference": "95c29b3756a23855a30566b745d218bee690bef2", "shasum": "" }, "require": { @@ -3264,11 +3565,12 @@ "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2" + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" }, "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, "bin": [ @@ -3312,9 +3614,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.14" }, - "time": "2025-08-04T12:39:37+00:00" + "time": "2025-10-27T17:15:31+00:00" }, { "name": "ralouphie/getallheaders", @@ -3514,6 +3816,72 @@ }, "time": "2025-09-04T20:59:21+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.9.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + }, + "time": "2025-07-11T13:20:48+00:00" + }, { "name": "spatie/temporary-directory", "version": "2.3.0", @@ -3577,16 +3945,16 @@ }, { "name": "symfony/cache", - "version": "v7.3.2", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6" + "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", - "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "url": "https://api.github.com/repos/symfony/cache/zipball/1277a1ec61c8d93ea61b2a59738f1deb9bfb6701", + "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701", "shasum": "" }, "require": { @@ -3655,7 +4023,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.2" + "source": "https://github.com/symfony/cache/tree/v7.3.6" }, "funding": [ { @@ -3675,7 +4043,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:13:41+00:00" + "time": "2025-10-30T13:22:58+00:00" }, { "name": "symfony/cache-contracts", @@ -3829,16 +4197,16 @@ }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -3903,7 +4271,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -3923,20 +4291,20 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "84321188c4754e64273b46b406081ad9b18e8614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", + "reference": "84321188c4754e64273b46b406081ad9b18e8614", "shasum": "" }, "require": { @@ -3972,7 +4340,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.6" }, "funding": [ { @@ -3983,12 +4351,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-29T17:24:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4059,16 +4431,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.2", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", - "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", "shasum": "" }, "require": { @@ -4116,7 +4488,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.2" + "source": "https://github.com/symfony/error-handler/tree/v7.3.6" }, "funding": [ { @@ -4136,7 +4508,7 @@ "type": "tidelift" } ], - "time": "2025-07-07T08:17:57+00:00" + "time": "2025-10-31T19:12:50+00:00" }, { "name": "symfony/event-dispatcher", @@ -4300,16 +4672,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -4344,7 +4716,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -4364,20 +4736,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.3", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", - "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6379e490d6ecfc5c4224ff3a754b90495ecd135c", + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c", "shasum": "" }, "require": { @@ -4427,7 +4799,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.6" }, "funding": [ { @@ -4447,20 +4819,20 @@ "type": "tidelift" } ], - "time": "2025-08-20T08:04:18+00:00" + "time": "2025-11-06T11:05:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.3", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b" + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b", - "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9a34dc0196677250e3609c2fac9de9e1551a262", + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262", "shasum": "" }, "require": { @@ -4545,7 +4917,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.6" }, "funding": [ { @@ -4565,20 +4937,20 @@ "type": "tidelift" } ], - "time": "2025-08-29T08:23:45+00:00" + "time": "2025-11-06T20:58:12+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575" + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575", - "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575", + "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", "shasum": "" }, "require": { @@ -4629,7 +5001,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.3" + "source": "https://github.com/symfony/mailer/tree/v7.3.5" }, "funding": [ { @@ -4649,20 +5021,20 @@ "type": "tidelift" } ], - "time": "2025-08-13T11:49:31+00:00" + "time": "2025-10-24T14:27:20+00:00" }, { "name": "symfony/mime", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", - "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", "shasum": "" }, "require": { @@ -4717,7 +5089,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.2" + "source": "https://github.com/symfony/mime/tree/v7.3.4" }, "funding": [ { @@ -4737,7 +5109,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5570,16 +5942,16 @@ }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -5611,7 +5983,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -5631,20 +6003,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:42:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/routing", - "version": "v7.3.2", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091", + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091", "shasum": "" }, "require": { @@ -5696,7 +6068,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.2" + "source": "https://github.com/symfony/routing/tree/v7.3.6" }, "funding": [ { @@ -5716,20 +6088,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-11-05T07:57:47+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -5783,7 +6155,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -5794,25 +6166,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -5827,7 +6203,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -5870,7 +6245,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -5890,20 +6265,20 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e0837b4cbcef63c754d89a4806575cada743a38d" + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d", - "reference": "e0837b4cbcef63c754d89a4806575cada743a38d", + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", "shasum": "" }, "require": { @@ -5970,7 +6345,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.3" + "source": "https://github.com/symfony/translation/tree/v7.3.4" }, "funding": [ { @@ -5990,20 +6365,20 @@ "type": "tidelift" } ], - "time": "2025-08-01T21:02:37+00:00" + "time": "2025-09-07T11:39:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -6052,7 +6427,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -6063,12 +6438,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/uid", @@ -6146,16 +6525,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", - "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -6209,7 +6588,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -6229,20 +6608,20 @@ "type": "tidelift" } ], - "time": "2025-08-13T11:49:31+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", - "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", "shasum": "" }, "require": { @@ -6290,7 +6669,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.3" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" }, "funding": [ { @@ -6310,7 +6689,7 @@ "type": "tidelift" } ], - "time": "2025-08-18T13:10:53+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -6524,64 +6903,6 @@ } ], "time": "2024-11-21T01:49:47+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" } ], "packages-dev": [ @@ -7466,16 +7787,16 @@ }, { "name": "laravel-lang/lang", - "version": "15.24.5", + "version": "15.26.2", "source": { "type": "git", "url": "https://github.com/Laravel-Lang/lang.git", - "reference": "495096372a2c45a69f9a2759973495c9d013367f" + "reference": "4f49e4a77ced9ace7955db2159234e4a9c0b22a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/495096372a2c45a69f9a2759973495c9d013367f", - "reference": "495096372a2c45a69f9a2759973495c9d013367f", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/4f49e4a77ced9ace7955db2159234e4a9c0b22a3", + "reference": "4f49e4a77ced9ace7955db2159234e4a9c0b22a3", "shasum": "" }, "require": { @@ -7526,7 +7847,7 @@ "issues": "https://github.com/Laravel-Lang/lang/issues", "source": "https://github.com/Laravel-Lang/lang" }, - "time": "2025-09-10T20:41:54+00:00" + "time": "2025-10-29T12:19:07+00:00" }, { "name": "laravel-lang/locale-list", @@ -8105,16 +8426,16 @@ }, { "name": "laravel/pint", - "version": "v1.24.0", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", "shasum": "" }, "require": { @@ -8125,9 +8446,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.82.2", - "illuminate/view": "^11.45.1", - "larastan/larastan": "^3.5.0", + "friendsofphp/php-cs-fixer": "^3.87.2", + "illuminate/view": "^11.46.0", + "larastan/larastan": "^3.7.1", "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", @@ -8138,9 +8459,6 @@ ], "type": "project", "autoload": { - "files": [ - "overrides/Runner/Parallel/ProcessFactory.php" - ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -8170,20 +8488,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-10T18:09:32+00:00" + "time": "2025-09-19T02:57:12+00:00" }, { "name": "laravel/sail", - "version": "v1.45.0", + "version": "v1.47.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "019a2933ff4a9199f098d4259713f9bc266a874e" + "reference": "9a11e822238167ad8b791e4ea51155d25cf4d8f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/019a2933ff4a9199f098d4259713f9bc266a874e", - "reference": "019a2933ff4a9199f098d4259713f9bc266a874e", + "url": "https://api.github.com/repos/laravel/sail/zipball/9a11e822238167ad8b791e4ea51155d25cf4d8f2", + "reference": "9a11e822238167ad8b791e4ea51155d25cf4d8f2", "shasum": "" }, "require": { @@ -8196,7 +8514,7 @@ }, "require-dev": { "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^2.0" }, "bin": [ "bin/sail" @@ -8233,7 +8551,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-08-25T19:28:31+00:00" + "time": "2025-10-28T13:55:29+00:00" }, { "name": "mockery/mockery", @@ -10124,16 +10442,16 @@ }, { "name": "sebastian/exporter", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { @@ -10147,7 +10465,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -10190,15 +10508,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-12-05T09:17:50+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", @@ -10767,16 +11097,16 @@ }, { "name": "symfony/yaml", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", "shasum": "" }, "require": { @@ -10819,7 +11149,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.3" + "source": "https://github.com/symfony/yaml/tree/v7.3.5" }, "funding": [ { @@ -10839,7 +11169,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T11:34:33+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", @@ -10949,6 +11279,64 @@ } ], "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.12.1" + }, + "time": "2025-10-29T15:56:20+00:00" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index 03b946d..fbd3f30 100644 --- a/config/app.php +++ b/config/app.php @@ -123,5 +123,7 @@ 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], + + ]; diff --git a/config/filesystems.php b/config/filesystems.php index 3d671bd..d3a658b 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -76,5 +76,11 @@ 'links' => [ public_path('storage') => storage_path('app/public'), ], + 'links' => [ + public_path('storage') => storage_path('app\public'), + public_path('logo') => storage_path('app\logo'), + public_path('produits') => storage_path('app\produits'), + public_path('publications') => storage_path('app\PUBLICATIONS'), + ], ]; diff --git a/config/services.php b/config/services.php index 24b781b..8d9105f 100644 --- a/config/services.php +++ b/config/services.php @@ -42,5 +42,10 @@ 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), ], ], + 'fedapay' => [ + 'public_key' => env('FEDAPAY_PUBLIC_KEY'), + 'secret_key' => env('FEDAPAY_SECRET_KEY'), + ], + ]; diff --git a/database/migrations/2025_09_09_080822_create_products_table.php b/database/migrations/2025_09_09_080822_create_products_table.php index b553742..61c6661 100644 --- a/database/migrations/2025_09_09_080822_create_products_table.php +++ b/database/migrations/2025_09_09_080822_create_products_table.php @@ -17,7 +17,9 @@ public function up(): void $table->text('description'); $table->integer('prix'); $table->integer('qte')->default(0); - $table->enum('status_stock',['en_stock','rupture_de_stock']); + // ✅ Modifier cette ligne + $table->enum('status_stock', ['in_stock', 'low_stock', 'out_of_stock']) + ->default('in_stock'); $table->string('path_img'); $table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->timestamps(); diff --git a/database/migrations/2025_10_19_090930_add_termine_to_user_quizzs_table.php b/database/migrations/2025_10_19_090930_add_termine_to_user_quizzs_table.php new file mode 100644 index 0000000..1f8d968 --- /dev/null +++ b/database/migrations/2025_10_19_090930_add_termine_to_user_quizzs_table.php @@ -0,0 +1,25 @@ +boolean('termine')->default(false)->after('score'); + }); +} + +public function down() +{ + Schema::table('user__quizzs', function (Blueprint $table) { + $table->dropColumn('termine'); + }); +} +}; diff --git a/database/migrations/2025_10_20_113808_add_reponses_to_user_quizz_table.php b/database/migrations/2025_10_20_113808_add_reponses_to_user_quizz_table.php new file mode 100644 index 0000000..0100005 --- /dev/null +++ b/database/migrations/2025_10_20_113808_add_reponses_to_user_quizz_table.php @@ -0,0 +1,28 @@ +json('reponses')->nullable(); + }); +} + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('user__quizzs', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2025_10_23_071658_add_order_support_to_paiements_table.php b/database/migrations/2025_10_23_071658_add_order_support_to_paiements_table.php new file mode 100644 index 0000000..c4c8d9e --- /dev/null +++ b/database/migrations/2025_10_23_071658_add_order_support_to_paiements_table.php @@ -0,0 +1,30 @@ +foreignId('order_id')->nullable()->after('formation_id')->constrained()->onDelete('cascade'); + + // Ajouter le type de paiement + $table->enum('type', ['formation', 'order'])->default('formation')->after('order_id'); + + // Modifier formation_id pour être nullable + $table->unsignedBigInteger('formation_id')->nullable()->change(); + }); + } + + public function down() + { + Schema::table('paiements', function (Blueprint $table) { + $table->dropForeign(['order_id']); + $table->dropColumn(['order_id', 'type']); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2025_10_23_072032_remove_paiement_id_from_orders_table.php b/database/migrations/2025_10_23_072032_remove_paiement_id_from_orders_table.php new file mode 100644 index 0000000..71b69b1 --- /dev/null +++ b/database/migrations/2025_10_23_072032_remove_paiement_id_from_orders_table.php @@ -0,0 +1,36 @@ +dropForeign(['paiement_id']); + + // Puis supprime la colonne + $table->dropColumn('paiement_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + // On recrée la colonne + $table->unsignedBigInteger('paiement_id')->nullable(); + + // Et on rétablit la clé étrangère si besoin + $table->foreign('paiement_id')->references('id')->on('paiements')->onDelete('cascade'); + }); + } +}; diff --git a/database/migrations/2025_11_08_102314_add_order_code_to_orders_table.php b/database/migrations/2025_11_08_102314_add_order_code_to_orders_table.php new file mode 100644 index 0000000..bd6f0ae --- /dev/null +++ b/database/migrations/2025_11_08_102314_add_order_code_to_orders_table.php @@ -0,0 +1,22 @@ +string('order_code')->unique()->after('id'); + }); + } + + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->dropColumn('order_code'); + }); + } +}; \ No newline at end of file diff --git a/lorem b/lorem deleted file mode 100644 index 16d66d4..0000000 --- a/lorem +++ /dev/null @@ -1,581 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -onclick="window.location.href='{{ route('quiz.manage', $module->id) }}'" \ No newline at end of file diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..a8093be --- /dev/null +++ b/public/app.js @@ -0,0 +1,7 @@ +import './bootstrap'; + +import Alpine from 'alpinejs'; + +window.Alpine = Alpine; + +Alpine.start(); diff --git a/public/assets/img/11.jpg b/public/assets/img/11.jpg new file mode 100644 index 0000000..bcacc34 Binary files /dev/null and b/public/assets/img/11.jpg differ diff --git a/public/assets/img/12.jpg b/public/assets/img/12.jpg new file mode 100644 index 0000000..6f52ca7 Binary files /dev/null and b/public/assets/img/12.jpg differ diff --git a/public/assets/img/13.jpg b/public/assets/img/13.jpg new file mode 100644 index 0000000..b6b0fc9 Binary files /dev/null and b/public/assets/img/13.jpg differ diff --git a/public/assets/img/14.jpg b/public/assets/img/14.jpg new file mode 100644 index 0000000..e952dc2 Binary files /dev/null and b/public/assets/img/14.jpg differ diff --git a/public/assets/img/scene-d-une-ferme-de-poulets-avec-des-volailles-et-des-gens.jpg b/public/assets/img/5.jpg similarity index 100% rename from public/assets/img/scene-d-une-ferme-de-poulets-avec-des-volailles-et-des-gens.jpg rename to public/assets/img/5.jpg diff --git a/public/assets/img/portrait-photorealiste-d-une-femme-africaine.jpg b/public/assets/img/6.jpg similarity index 100% rename from public/assets/img/portrait-photorealiste-d-une-femme-africaine.jpg rename to public/assets/img/6.jpg diff --git a/public/assets/img/9.jpg b/public/assets/img/9.jpg new file mode 100644 index 0000000..1d108b8 Binary files /dev/null and b/public/assets/img/9.jpg differ diff --git a/public/assets/img/scene-photorealiste-d-une-ferme-avicole-avec-des-poulets.jpg b/public/assets/img/scene-photorealiste-d-une-ferme-avicole-avec-des-poulets.jpg deleted file mode 100644 index 2c3fe0d..0000000 Binary files a/public/assets/img/scene-photorealiste-d-une-ferme-avicole-avec-des-poulets.jpg and /dev/null differ diff --git a/public/assets/img/vue-d-une-femme-travaillant-dans-le-domaine-de-l-elevage-pour-celebrer-la-fete-du-travail-pour-les-femmes.jpg b/public/assets/img/vue-d-une-femme-travaillant-dans-le-domaine-de-l-elevage-pour-celebrer-la-fete-du-travail-pour-les-femmes.jpg new file mode 100644 index 0000000..3d26387 Binary files /dev/null and b/public/assets/img/vue-d-une-femme-travaillant-dans-le-domaine-de-l-elevage-pour-celebrer-la-fete-du-travail-pour-les-femmes.jpg differ diff --git a/public/attestations/attestation_1_1.pdf b/public/attestations/attestation_1_1.pdf new file mode 100644 index 0000000..d139fea Binary files /dev/null and b/public/attestations/attestation_1_1.pdf differ diff --git a/public/attestations/attestation_2_1.pdf b/public/attestations/attestation_2_1.pdf new file mode 100644 index 0000000..faa21b8 Binary files /dev/null and b/public/attestations/attestation_2_1.pdf differ diff --git a/public/images/farm/lapin1.jpg b/public/images/farm/lapin1.jpg new file mode 100644 index 0000000..0d2e00f Binary files /dev/null and b/public/images/farm/lapin1.jpg differ diff --git a/public/images/farm/lapin10.jpg b/public/images/farm/lapin10.jpg new file mode 100644 index 0000000..0388a98 Binary files /dev/null and b/public/images/farm/lapin10.jpg differ diff --git a/public/images/farm/lapin2.jpg b/public/images/farm/lapin2.jpg new file mode 100644 index 0000000..45524e9 Binary files /dev/null and b/public/images/farm/lapin2.jpg differ diff --git a/public/images/farm/lapin3.jpg b/public/images/farm/lapin3.jpg new file mode 100644 index 0000000..ba60979 Binary files /dev/null and b/public/images/farm/lapin3.jpg differ diff --git a/public/images/farm/lapin4.jpg b/public/images/farm/lapin4.jpg new file mode 100644 index 0000000..3e6247f Binary files /dev/null and b/public/images/farm/lapin4.jpg differ diff --git a/public/images/farm/lapin5.jpg b/public/images/farm/lapin5.jpg new file mode 100644 index 0000000..25e2976 Binary files /dev/null and b/public/images/farm/lapin5.jpg differ diff --git a/public/images/farm/lapin6.jpg b/public/images/farm/lapin6.jpg new file mode 100644 index 0000000..4b4cf20 Binary files /dev/null and b/public/images/farm/lapin6.jpg differ diff --git a/public/images/farm/lapin7.jpg b/public/images/farm/lapin7.jpg new file mode 100644 index 0000000..0606a31 Binary files /dev/null and b/public/images/farm/lapin7.jpg differ diff --git a/public/images/farm/lapin8.jpg b/public/images/farm/lapin8.jpg new file mode 100644 index 0000000..de41c67 Binary files /dev/null and b/public/images/farm/lapin8.jpg differ diff --git a/public/images/farm/lapin9.jpg b/public/images/farm/lapin9.jpg new file mode 100644 index 0000000..3855c3b Binary files /dev/null and b/public/images/farm/lapin9.jpg differ diff --git a/public/images/farm/oeufs1.jpg b/public/images/farm/oeufs1.jpg new file mode 100644 index 0000000..cda2fad Binary files /dev/null and b/public/images/farm/oeufs1.jpg differ diff --git a/public/images/farm/oeufs2.jpg b/public/images/farm/oeufs2.jpg new file mode 100644 index 0000000..8d3b1ee Binary files /dev/null and b/public/images/farm/oeufs2.jpg differ diff --git a/public/images/farm/papayer1.jpg b/public/images/farm/papayer1.jpg new file mode 100644 index 0000000..e363161 Binary files /dev/null and b/public/images/farm/papayer1.jpg differ diff --git a/public/images/farm/poulailler1.jpg b/public/images/farm/poulailler1.jpg new file mode 100644 index 0000000..80e1694 Binary files /dev/null and b/public/images/farm/poulailler1.jpg differ diff --git a/public/images/farm/poulailler2.jpg b/public/images/farm/poulailler2.jpg new file mode 100644 index 0000000..d867a95 Binary files /dev/null and b/public/images/farm/poulailler2.jpg differ diff --git a/public/images/farm/poulailler3.jpg b/public/images/farm/poulailler3.jpg new file mode 100644 index 0000000..085709f Binary files /dev/null and b/public/images/farm/poulailler3.jpg differ diff --git a/public/images/farm/poulailler4.jpg b/public/images/farm/poulailler4.jpg new file mode 100644 index 0000000..3ebe81e Binary files /dev/null and b/public/images/farm/poulailler4.jpg differ diff --git a/public/images/farm/poulailler5.jpg b/public/images/farm/poulailler5.jpg new file mode 100644 index 0000000..80e1694 Binary files /dev/null and b/public/images/farm/poulailler5.jpg differ diff --git a/public/images/farm/poulailler6.jpg b/public/images/farm/poulailler6.jpg new file mode 100644 index 0000000..88dd798 Binary files /dev/null and b/public/images/farm/poulailler6.jpg differ diff --git a/public/images/farm/poulailler7.jpg b/public/images/farm/poulailler7.jpg new file mode 100644 index 0000000..092ea9e Binary files /dev/null and b/public/images/farm/poulailler7.jpg differ diff --git a/public/images/farm/poulailler8.jpg b/public/images/farm/poulailler8.jpg new file mode 100644 index 0000000..fadf664 Binary files /dev/null and b/public/images/farm/poulailler8.jpg differ diff --git a/public/images/farm/poulailler9.jpg b/public/images/farm/poulailler9.jpg new file mode 100644 index 0000000..529b786 Binary files /dev/null and b/public/images/farm/poulailler9.jpg differ diff --git a/public/images/smalland-1.png b/public/images/smalland-1.png new file mode 100644 index 0000000..9f87056 Binary files /dev/null and b/public/images/smalland-1.png differ diff --git a/public/images/smalland.jpeg b/public/images/smalland.jpeg new file mode 100644 index 0000000..a9ce2d6 Binary files /dev/null and b/public/images/smalland.jpeg differ diff --git a/public/images/tree.png b/public/images/tree.png new file mode 100644 index 0000000..30a4900 Binary files /dev/null and b/public/images/tree.png differ diff --git a/public/images/tree1.png b/public/images/tree1.png new file mode 100644 index 0000000..e9733a3 Binary files /dev/null and b/public/images/tree1.png differ diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 5f1390b..4250a53 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -2,3 +2,8 @@ import axios from 'axios'; window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + +let token = document.head.querySelector('meta[name="csrf-token"]'); +if (token) { + window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; +} diff --git a/resources/views/admin/layout/blog/TEST b/resources/views/admin/layout/blog/TEST new file mode 100644 index 0000000..52723dc --- /dev/null +++ b/resources/views/admin/layout/blog/TEST @@ -0,0 +1,691 @@ + + + + + + + Gestion des Publications + + + + + + + +
+
+
+
+

Administration

+
+ +
+
+
+ + +
+
+ + + + + + + +
+

+ Créer une nouvelle publication +

+

Remplissez le formulaire ci-dessous pour créer ou modifier une publication

+
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+ +

Cliquez pour télécharger une image

+ +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+
+
+ + + + +@extends('admin.master') + +@section('content') +
+
+
+
+ + {{-- En-tête avec animation --}} +
+
+ + + +
+

+ {{ isset($publication) ? 'Modifier la publication' : 'Créer une nouvelle publication' }} +

+

Remplissez les informations ci-dessous

+
+ + {{-- Messages de notification --}} + @if(session('success')) +
+
+ + + + {{ session('success') }} +
+
+ @endif + + @if($errors->any()) +
+
+ + + +
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+
+ @endif + + {{-- Formulaire principal --}} +
+
+ +
+ + @csrf + @if(isset($publication)) + @method('PUT') + @endif + + {{-- Section Informations principales --}} +
+
+ 📝 Informations principales +
+ +
+
+ + +
+ +
+ + +
+
+
+ + {{-- Section Média --}} +
+
+ 🖼️ Média +
+ +
+
+ + +
+ + + @if(isset($publication) && $publication->image_path) +
+

Image actuelle :

+ Image actuelle +
+ @endif +
+
+
+
+ + {{-- Section Métadonnées --}} +
+
+ ⚙️ Métadonnées +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {{-- Boutons d'action --}} +
+ + ← Annuler + + + +
+ +
+ +
+
+ + {{-- Footer info --}} +
+

+ 💡 Astuce : Sauvegardez régulièrement votre travail en tant que brouillon +

+
+ +
+
+
+
+ + +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/blog/create-article.blade.php b/resources/views/admin/layout/blog/create-article.blade.php index d290912..ad1cc27 100644 --- a/resources/views/admin/layout/blog/create-article.blade.php +++ b/resources/views/admin/layout/blog/create-article.blade.php @@ -1,98 +1,259 @@ @extends('admin.master') @section('content') -
- - {{-- Message de succès --}} - @if(session('success')) -
{{ session('success') }}
- @endif - - {{-- Message d'erreur --}} - @if($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
- @endif +
+ +
+
+ + {{-- Success Message --}} + @if(session('success')) +
+
+ + + + {{ session('success') }} +
+
+ @endif -

- {{ isset($publication) ? 'Modifier la publication' : 'Créer une nouvelle publication' }} -

+ {{-- Error Message --}} + @if($errors->any()) +
+
+ + + +
+

Erreurs détectées:

+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+
+
+ @endif -
+ {{-- Page Title --}} +
+

+ {{ isset($publication) ? 'Modifier la publication' : 'Créer une nouvelle publication' }} +

+

Remplissez le formulaire ci-dessous pour créer ou modifier une publication

+
- @csrf - @if(isset($publication)) - @method('PUT') - @endif + {{-- Publication Form --}} + + + @csrf + @if(isset($publication)) + @method('PUT') + @endif -
- - -
+ {{-- Titre Field --}} +
+ + +
-
- - -
+ {{-- Content Field --}} +
+ + +
-
- - - @if(isset($publication) && $publication->image_path) -
- Image actuelle + {{-- Image Field --}} +
+ +
+ +
+ + + +

Cliquez pour télécharger une image

+ +
+
+ + {{-- Preview de la nouvelle image --}} + + + {{-- Image actuelle --}} + @if(isset($publication) && $publication->image_path) +
+

Image actuelle:

+ Image actuelle +
+ @endif
- @endif -
-
- - -
+ {{-- Category Field --}} +
+ + +
-
- - -
+ {{-- Author Field --}} +
+ + +
-
- - -
+ {{-- Tags Field --}} +
+ + +
-
- - -
+ {{-- Status Field --}} +
+ + +
-
- Annuler - -
+ {{-- Form Actions --}} +
+ + + + + Retour aux publications + + +
+ + - +
+
-@endsection + + + + +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/blog/create-article2.blade.php b/resources/views/admin/layout/blog/create-article2.blade.php new file mode 100644 index 0000000..d290912 --- /dev/null +++ b/resources/views/admin/layout/blog/create-article2.blade.php @@ -0,0 +1,98 @@ +@extends('admin.master') + +@section('content') +
+ + {{-- Message de succès --}} + @if(session('success')) +
{{ session('success') }}
+ @endif + + {{-- Message d'erreur --}} + @if($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +

+ {{ isset($publication) ? 'Modifier la publication' : 'Créer une nouvelle publication' }} +

+ +
+ + @csrf + @if(isset($publication)) + @method('PUT') + @endif + +
+ + +
+ +
+ + +
+ +
+ + + @if(isset($publication) && $publication->image_path) +
+ Image actuelle +
+ @endif +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Annuler + +
+ +
+
+@endsection diff --git a/resources/views/admin/layout/boutique/add_product.blade.php b/resources/views/admin/layout/boutique/add_product.blade.php index f58dc56..51f88d7 100644 --- a/resources/views/admin/layout/boutique/add_product.blade.php +++ b/resources/views/admin/layout/boutique/add_product.blade.php @@ -1,6 +1,24 @@ @extends('admin.master') @section('content') +{{-- Message de succès --}} +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +{{-- Affichage des erreurs --}} +@if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • - {{ $error }}
  • + @endforeach +
+
+@endif +

Ajouter un produit

@@ -39,8 +57,9 @@
diff --git a/resources/views/admin/layout/boutique/clients/index.blade.php b/resources/views/admin/layout/boutique/clients/index.blade.php new file mode 100644 index 0000000..1a60ef9 --- /dev/null +++ b/resources/views/admin/layout/boutique/clients/index.blade.php @@ -0,0 +1,208 @@ +@extends('admin.master') + +@section('content') + +
+ +
+
+

Gestion des clients

+

Liste de tous les clients inscrits

+
+ +
+ + +
+
+
+
+
Total clients
+

{{ $stats['total'] }}

+
+
+
+
+
+
+
Clients actifs
+

{{ $stats['with_orders'] }}

+
+
+
+
+
+
+
Sans commande
+

{{ $stats['without_orders'] }}

+
+
+
+
+
+
+
Ce mois
+

{{ $stats['this_month'] }}

+
+
+
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + + @forelse($clients as $client) + + + + + + + + + + @empty + + + + @endforelse + +
IDNomEmailTéléphoneCommandesInscriptionActions
#{{ $client->id }} +
+
+ {{ strtoupper(substr($client->name, 0, 1)) }} +
+
+ {{ $client->nom }} {{ $client->prenom}} + @if($client->orders_count > 0) +
+ Client actif + + @endif +
+
+
+ + {{ $client->email }} + + + @if($client->telephone) + + {{ $client->telephone }} + + @else + N/A + @endif + + @if($client->orders_count > 0) + + {{ $client->orders_count }} commande(s) + + @else + Aucune + @endif + + {{ $client->created_at->format('d/m/Y') }}
+ {{ $client->created_at->diffForHumans() }} +
+ +
+ +

Aucun client trouvé

+
+
+ + +
+ {{ $clients->appends(request()->query())->links() }} +
+
+
+
+ + + +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/boutique/clients/show.blade.php b/resources/views/admin/layout/boutique/clients/show.blade.php new file mode 100644 index 0000000..c93fceb --- /dev/null +++ b/resources/views/admin/layout/boutique/clients/show.blade.php @@ -0,0 +1,255 @@ +@extends('admin.master') + +@section('content') + +
+ +
+
+ +

Profil de {{ $client->nom }} {{ $client->prenom}}

+
+ +
+ +
+ +
+ +
+
+
+ {{ strtoupper(substr($client->nom, 0, 2)) }} +
+

{{ $client->nom }} {{ $client->prenom}}

+

Client depuis {{ $client->created_at->format('M Y') }}

+ +
+ +
+ + + @if($client->telephone) +
+ +

+ + {{ $client->telephone }} + +

+
+ @endif + +
+ +

+ {{ $client->created_at->format('d/m/Y') }} +
{{ $client->created_at->diffForHumans() }} +

+
+
+ +
+ +
+ + Envoyer un email + + @if($client->telephone) + + Appeler + + @endif +
+
+
+ + +
+
+
Statistiques
+
+
+
+
+ Total commandes + {{ $clientStats['total_commandes'] }} +
+
+
+
+ Montant total + {{ number_format($clientStats['montant_total'], 0, ',', ' ') }} FCFA +
+
+
+
+ Commande moyenne + {{ number_format($clientStats['commande_moyenne'], 0, ',', ' ') }} FCFA +
+
+ @if($clientStats['derniere_commande']) +
+
+ Dernière commande + {{ $clientStats['derniere_commande']->format('d/m/Y') }} +
+
+ @endif +
+
+
+ + +
+ +
+
+
Historique des commandes ({{ $client->orders->count() }})
+
+
+ @if($client->orders->count() > 0) +
+ + + + + + + + + + + + + @foreach($client->orders as $order) + + + + + + + + + @endforeach + +
CodeDateArticlesMontantStatutActions
{{ $order->order_code }} + {{ $order->created_at->format('d/m/Y') }}
+ {{ $order->created_at->format('H:i') }} +
+ {{ $order->products->count() }} article(s) + {{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA + + {{ $order->statusLabel }} + + + + + +
+
+ @else +
+ +

Aucune commande pour ce client

+
+ @endif +
+
+ + +
+
+
Historique des paiements ({{ $client->paiements->count() }})
+
+
+ @if($client->paiements->count() > 0) +
+ + + + + + + + + + + + + @foreach($client->paiements as $paiement) + + + + + + + + + @endforeach + +
TypeRéférenceMontantMéthodeStatutDate
+ @if($paiement->type === 'formation') + Formation + @else + Commande + @endif + + @if($paiement->type === 'formation' && $paiement->formation) + {{ Str::limit($paiement->formation->titre ?? 'N/A', 30) }} + @elseif($paiement->type === 'order' && $paiement->order) + {{ $paiement->order->order_code ?? 'N/A' }} + @endif + {{ number_format($paiement->montant_payé, 0, ',', ' ') }} FCFA{{ ucfirst($paiement->moyen_de_paiment) }} + @if($paiement->status === 'completed') + Complété + @else + En attente + @endif + + {{ $paiement->created_at->format('d/m/Y') }} +
+
+ @else +
+ +

Aucun paiement pour ce client

+
+ @endif +
+
+
+
+
+ + + +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/boutique/list_categorie.blade.php b/resources/views/admin/layout/boutique/list_categorie.blade.php index 115a64d..5909cee 100644 --- a/resources/views/admin/layout/boutique/list_categorie.blade.php +++ b/resources/views/admin/layout/boutique/list_categorie.blade.php @@ -9,23 +9,82 @@
+ {{-- Message de succès --}} +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +{{-- Affichage des erreurs --}} +@if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • - {{ $error }}
  • + @endforeach +
+
+@endif +
- +
+ @foreach($categories as $categorie) - + - + + @endforeach
ID NomActions
{{ $categorie->id }}{{ $categorie->nom }}{{ $categorie->nom }} +
+ @csrf + @method('DELETE') + +
+
+ +{{-- jQuery pour AJAX inline edit --}} +@section('scripts') + + +@endsection + @endsection diff --git a/resources/views/admin/layout/boutique/list_paiements.blade.php b/resources/views/admin/layout/boutique/list_paiements.blade.php new file mode 100644 index 0000000..c5c80cc --- /dev/null +++ b/resources/views/admin/layout/boutique/list_paiements.blade.php @@ -0,0 +1,51 @@ +@extends('admin.master') + +@section('content') + +
+
+

+ Liste des Paiements +

+ +
+ + + + + + + + + + + + + @forelse($paiements as $paiement) + + + + + + + + + @empty + + + + @endforelse + +
#UtilisateurCommandeMontantMoyenDate
{{ $loop->iteration + ($paiements->currentPage() - 1) * $paiements->perPage() }}{{ $paiement->user->nom }} {{$paiement->user->prenom}}{{ $paiement->Order->order_code ?? '—' }}{{ number_format($paiement->montant_payé, 0, ',', ' ') }} FCFA{{ ucfirst($paiement->moyen_de_paiment) }}{{ $paiement->created_at->format('d/m/Y H:i') }}
+ Aucun paiement trouvé. +
+
+ +
+ {{ $paiements->links() }} +
+
+
+ + +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/boutique/list_produit.blade.php b/resources/views/admin/layout/boutique/list_produit.blade.php index fc5513a..bbfbc27 100644 --- a/resources/views/admin/layout/boutique/list_produit.blade.php +++ b/resources/views/admin/layout/boutique/list_produit.blade.php @@ -2,6 +2,7 @@ @section('content')
+

Liste des Produits

@@ -23,24 +24,109 @@ Quantité Catégorie Statut + Actions @foreach($produits as $produit) - - Image produit - - {{ $produit->nom }} - {{ number_format($produit->prix, 0, ',', ' ') }} FCFA - {{ $produit->qte }} - {{ $produit->category->nom ?? '—' }} - - - {{ ucfirst($produit->statut_stock) }} - - - + + + {{ $produit->nom }} + {{ number_format($produit->prix, 0, ',', ' ') }} FCFA + {{ $produit->qte }} + {{ $produit->category->nom ?? '—' }} + + @if($produit->qte >= 5) + Disponible + @elseif($produit->qte > 0) + Limite + @else + Rupture + @endif + + + + + + +
+ @csrf + @method('DELETE') + +
+ + + + + + @endforeach diff --git a/resources/views/admin/layout/boutique/orders/index.blade.php b/resources/views/admin/layout/boutique/orders/index.blade.php new file mode 100644 index 0000000..2df863c --- /dev/null +++ b/resources/views/admin/layout/boutique/orders/index.blade.php @@ -0,0 +1,241 @@ +@extends('admin.master') + +@section('content') + + + +
+ + + + @if(session('success')) +
+ {{ session('success') }} + +
+ @endif + + +
+
+
+
+
Total
+

{{ $stats['total'] }}

+
+
+
+
+
+
+
En attente
+

{{ $stats['pending'] }}

+
+
+
+
+
+
+
Confirmées
+

{{ $stats['confirmed'] }}

+
+
+
+
+
+
+
En préparation
+

{{ $stats['processing'] }}

+
+
+
+
+
+
+
Expédiées
+

{{ $stats['shipped'] }}

+
+
+
+
+
+
+
Livrées
+

{{ $stats['delivered'] }}

+
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + Réinitialiser + +
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + @forelse($orders as $order) + + + + + + + + + + + @empty + + + + @endforelse + +
CodeClientDateArticlesMontantPaiementStatutActions
+ {{ $order->order_code }} + +
+ {{ $order->user->name }}
+ {{ $order->user->email }}
+ + {{ $order->telephone }} + +
+
+ {{ $order->created_at->format('d/m/Y') }}
+ {{ $order->created_at->format('H:i') }} +
+ + {{ $order->products->count() }} article(s) + + + {{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA + + @if($order->paiement) + @if($order->paiement->status == 'completed') + + Payé + + @else + + En attente + + @endif + @else + + À la livraison + + @endif + + + {{ $order->statusLabel }} + + +
+ + + + + @if($order->paiement && $order->paiement->status != 'completed') +
+ @csrf + +
+ @endif +
+
+ +

Aucune commande trouvée

+
+
+ + +
+ {{ $orders->appends(request()->query())->links() }} +
+
+
+
+ +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/boutique/orders/show.blade.php b/resources/views/admin/layout/boutique/orders/show.blade.php new file mode 100644 index 0000000..da7ddce --- /dev/null +++ b/resources/views/admin/layout/boutique/orders/show.blade.php @@ -0,0 +1,276 @@ +@extends('admin.layout.master') + +@section('content') + +
+ +
+
+ +

Commande {{ $order->order_code }}

+
+ +
+ + @if(session('success')) +
+ {{ session('success') }} + +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} + +
+ @endif + +
+ +
+ +
+
+
+
Informations de la commande
+ + {{ $order->statusLabel }} + +
+
+
+
+
+ +

{{ $order->order_code }}

+
+
+ +

{{ $order->created_at->format('d/m/Y à H:i') }}

+
+
+ +

+ @if($order->mode_livraison == 'standard') + Livraison Standard + @elseif($order->mode_livraison == 'express') + Livraison Express + @else + Retrait en magasin + @endif +

+
+
+ +

+ {{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA +

+
+
+
+
+ + +
+
+
Produits commandés ({{ $order->products->count() }})
+
+
+
+ + + + + + + + + + + @foreach($order->products as $product) + + + + + + + @endforeach + + + + + + + +
ProduitPrix unitaireQuantitéTotal
+
+ @if($product->path_img) + {{ $product->nom }} + @endif +
+ {{ $product->nom }}
+ {{ Str::limit($product->description, 50) }} +
+
+
{{ number_format($product->prix, 0, ',', ' ') }} FCFA{{ $product->pivot->qte_commander }}{{ number_format($product->prix * $product->pivot->qte_commander, 0, ',', ' ') }} FCFA
Total{{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA
+
+
+
+ + +
+
+
Informations client et livraison
+
+
+
+
+
Client
+

+ {{ $order->user->name }}
+ {{ $order->user->email }}
+ {{ $order->telephone }} +

+
+
+
Adresse de livraison
+

{{ $order->addresse }}

+
+
+
+
+
+ + +
+ +
+
+
Modifier le statut
+
+
+
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ + +
+
+
+ + +
+
+
Paiement
+
+
+ @if($order->paiement) +
+ +

+ @if($order->paiement->status == 'completed') + + Payé + + @else + + En attente + + @endif +

+
+
+ +

{{ ucfirst($order->paiement->moyen_de_paiment) }}

+
+
+ +

{{ number_format($order->paiement->montant_payé, 0, ',', ' ') }} FCFA

+
+ @if($order->paiement->transaction_id) +
+ +

{{ $order->paiement->transaction_id }}

+
+ @endif + + @if($order->paiement->status != 'completed') +
+ @csrf + +
+ @endif + @else +
+ + Paiement à la livraison +
+ @endif +
+
+ + + +
+
+
+ +@endsection \ No newline at end of file diff --git a/resources/views/admin/layout/formations/certifications.blade.php b/resources/views/admin/layout/formations/certifications.blade.php index 08dd51e..f5b5d43 100644 --- a/resources/views/admin/layout/formations/certifications.blade.php +++ b/resources/views/admin/layout/formations/certifications.blade.php @@ -6,22 +6,22 @@ - - - - - - - + + + + + + + @forelse($certifications as $certification) - + - + - - - - - - - - - - - - - - - - - - + @foreach($order->products as $product) + + + + + + + @endforeach - - - - - - - - - +
#UtilisateurFormationDateProgressionAttestation
#UtilisateurFormationDateProgressionAttestation
{{ $loop->iteration }}{{ $certification->user->name ?? 'Inconnu' }}{{ $certification->user->nom}} {{ $certification->user->prenom}} {{ $certification->formation->titre ?? 'Inconnue' }}{{ $certification->created_at->format('d/m/Y H:i') }}{{ $certification->created_at->format('d/m/Y') }} {{ $certification->progression }}% @if($certification->path_attestation) diff --git a/resources/views/admin/layout/lessons/script.blade.php b/resources/views/admin/layout/lessons/script.blade.php index be9d539..30343fd 100644 --- a/resources/views/admin/layout/lessons/script.blade.php +++ b/resources/views/admin/layout/lessons/script.blade.php @@ -65,7 +65,6 @@ const submitBtn = editForm.querySelector('button[type="submit"]'); const originalBtnText = submitBtn.innerHTML; - // ⚡ Afficher spinner submitBtn.disabled = true; submitBtn.innerHTML = `Chargement...`; @@ -73,12 +72,24 @@ fetch(`/dashboard/lessons/${lessonId}`, { method: 'POST', headers: { - 'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value + 'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value, + 'Accept': 'application/json' }, body: formData }) - .then(res => res.json()) + .then(res => { + if (!res.ok) { + return res.json().then(errorData => { + throw { + status: res.status, + data: errorData + }; + }); + } + return res.json(); + }) .then(data => { + // Gestion du succès (même code que précédemment) if (!alertBox) return; if (data.success) { @@ -86,42 +97,27 @@ alertBox.textContent = data.message; alertBox.classList.remove('d-none'); - // Mettre à jour le titre dans le tableau - const row = document.querySelector( - `button[data-lesson-id="${lessonId}"]`)?.closest('tr'); - if (row) row.querySelector('td:nth-child(2)').textContent = data.lesson - .titre; - - // Mettre à jour la vidéo et le PDF dans le modal - const videoContainer = document.getElementById('currentVideoPreview'); - const pdfContainer = document.getElementById('currentPdfPreview'); - - videoContainer.innerHTML = data.lesson.video_url ? - `` : - `

Aucune vidéo disponible

`; - - pdfContainer.innerHTML = data.lesson.pdf_url ? - ` - Voir PDF - ` : - `

Aucun PDF disponible

`; - - } else { - alertBox.className = 'alert alert-danger'; - alertBox.textContent = 'Erreur lors de la mise à jour'; - alertBox.classList.remove('d-none'); + // ... reste du code de succès } }) - .catch(() => { + .catch(error => { if (!alertBox) return; + alertBox.className = 'alert alert-danger'; - alertBox.textContent = 'Erreur réseau'; + + if (error.status === 422 && error.data.errors) { + // Afficher la première erreur de validation + const firstError = Object.values(error.data.errors)[0][0]; + alertBox.textContent = firstError; + } else if (error.data && error.data.message) { + alertBox.textContent = error.data.message; + } else { + alertBox.textContent = 'Erreur réseau'; + } + alertBox.classList.remove('d-none'); }) .finally(() => { - // ⚡ Réinitialiser le bouton submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; }); @@ -245,4 +241,4 @@ function getDeleteAlert() { console.log('Script leçons chargé ✅'); }); - + \ No newline at end of file diff --git a/resources/views/admin/master.blade.php b/resources/views/admin/master.blade.php index 4b7590b..3d054d2 100644 --- a/resources/views/admin/master.blade.php +++ b/resources/views/admin/master.blade.php @@ -18,6 +18,8 @@ + + @stack('styles') diff --git a/resources/views/admin/partials/sidebar.blade.php b/resources/views/admin/partials/sidebar.blade.php index 9bc860c..d4f4b9e 100644 --- a/resources/views/admin/partials/sidebar.blade.php +++ b/resources/views/admin/partials/sidebar.blade.php @@ -1,13 +1,10 @@ + \ No newline at end of file diff --git a/resources/views/attestation/pdf.blade.php b/resources/views/attestation/pdf.blade.php new file mode 100644 index 0000000..828cb98 --- /dev/null +++ b/resources/views/attestation/pdf.blade.php @@ -0,0 +1,140 @@ + + + + + Attestation de Formation + + {{-- Font Awesome pour les icônes --}} + + + + + +
+ + {{-- Logo en haut --}} +
+ Logo Small Land +
+ +

Attestation de Formation

+

Ce certificat est décerné à :

+

{{ $user->nom}} {{$user->prenom}}

+ +
+

Pour avoir complété avec succès la formation :

+

{{ $formation->titre }}

+

Date de délivrance : {{ $date }}

+
+ +
+
+
+ Signature +
+
+ +
+ Small Land a voté +
+ + +
+ + \ No newline at end of file diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index cb32e08..576f9eb 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -1,25 +1,44 @@ - -
- {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }} +@extends('master') + +@section('content') +
+
+

Réinitialisation du mot de passe

+ + @if (session('status')) +
+ {{ session('status') }} +
+ @endif + +

+ Entrez votre adresse e-mail et nous vous enverrons un lien pour réinitialiser votre mot de passe. +

+ +
+ @csrf + +
+ + + + @error('email') + {{ $message }} + @enderror +
+ + +
+ +

+ Retour à la page de connexion +

+
+@endsection - - - -
- @csrf - - -
- - - -
- -
- - {{ __('Email Password Reset Link') }} - -
-
- diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 28880b9..3052fda 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,67 +1,67 @@ -@extends('Master') +@extends('master') @section('content') -
-
+
+
+

Connexion

- -
-

Connexion

-
+ @if ($errors->any()) + + @endif - -
- @if ($errors->any()) -
- {{ $errors->first() }} -
- @endif +
+ @csrf - - @csrf -
- - -
+
+ + +
- -
- -
- - -
+
+ +
+ +
+
- - + + - - +
- -@endsection +@endsection \ No newline at end of file diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 76becc0..76166da 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -1,104 +1,97 @@ -@extends('Master') +@extends('master') + @section('content') -
-
+
+
+

Création de compte

+ +
+ @csrf - -
-

Inscription

+
+
+ + + @error('nom') +
{{ $message }}
+ @enderror +
+
+ + + @error('prenom') +
{{ $message }}
+ @enderror +
- -
- - @csrf -
- - - @error('nom') - {{ $message }} - @enderror -
+
+ + + @error('email') +
{{ $message }}
+ @enderror +
-
- - - @error('prenom') - {{ $message }} - @enderror -
+
+ +
+ + +
+ @error('password') +
{{ $message }}
+ @enderror +
-
- - - @error('email') - {{ $message }} - @enderror -
+
+ +
+ + +
+ @error('password_confirmation') +
{{ $message }}
+ @enderror +
- -
- -
- - -
- @error('password') - {{ $message }} - @enderror -
+ + +
+
- -
- -
- - -
- @error('password_confirmation') - {{ $message }} - @enderror -
+ -@endsection + document.getElementById('togglePasswordConfirm').addEventListener('click', () => { + togglePasswordVisibility('password_confirmation', 'togglePasswordConfirm'); + }); + +@endsection \ No newline at end of file diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php index a6494cc..88e5e88 100644 --- a/resources/views/auth/reset-password.blade.php +++ b/resources/views/auth/reset-password.blade.php @@ -1,39 +1,63 @@ - -
- @csrf - - - - - -
- - - -
- - -
- - - -
- - -
- - - - - -
- -
- - {{ __('Reset Password') }} - -
-
-
+@extends('master') + +@section('content') +
+
+ + + Logo + +

Réinitialisation du mot de passe

+ +

+ Entrez votre nouvel e-mail et votre nouveau mot de passe ci-dessous. +

+ +
+ @csrf + + + + + +
+ + + + @error('email') + {{ $message }} + @enderror +
+ + +
+ + + + @error('password') + {{ $message }} + @enderror +
+ + +
+ + +
+ + + +
+ +

+ Retour à la page de connexion +

+
+
+@endsection diff --git a/resources/views/auth/verify-email.blade.php b/resources/views/auth/verify-email.blade.php index eaf811d..c2c625c 100644 --- a/resources/views/auth/verify-email.blade.php +++ b/resources/views/auth/verify-email.blade.php @@ -1,31 +1,37 @@ - -
- {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} -
+@extends('master') - @if (session('status') == 'verification-link-sent') -
- {{ __('A new verification link has been sent to the email address you provided during registration.') }} -
- @endif +@section('content') +
+
+
+

Vérification de l'adresse e-mail

-
-
- @csrf + @if (session('status') == 'verification-link-sent') +
+ ✅ Un nouveau lien de vérification a été envoyé à votre adresse e-mail. +
+ @endif -
- - {{ __('Resend Verification Email') }} - -
-
+

+ Avant de continuer, veuillez vérifier votre e-mail et cliquer sur le lien de confirmation. +
+ Si vous n’avez pas reçu le message, vous pouvez demander un nouveau lien ci-dessous. +

-
- @csrf + + @csrf + +
- - +
+ @csrf + +
+
- +
+@endsection diff --git a/resources/views/avis/edit.blade.php b/resources/views/avis/edit.blade.php index 1a6f46d..8f35a74 100644 --- a/resources/views/avis/edit.blade.php +++ b/resources/views/avis/edit.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.App') +@extends('Master') @section('content')
diff --git a/resources/views/emails/OrderStatusChanged.php b/resources/views/emails/OrderStatusChanged.php new file mode 100644 index 0000000..ce1b6ce --- /dev/null +++ b/resources/views/emails/OrderStatusChanged.php @@ -0,0 +1,166 @@ + + + + + + + +
+
+

Smalland

+

Mise à jour de votre commande

+
+ +
+

Bonjour {{ $order->user->name }},

+ +

Le statut de votre commande {{ $order->order_code }} a été mis à jour.

+ +
+

Statut de la commande

+ + @if($newStatus === 'confirmed') + ✓ Commande Confirmée +

+ Votre commande a été confirmée et est en cours de traitement. + Nous préparons vos articles avec soin. +

+ @elseif($newStatus === 'processing') + 📦 En Préparation +

+ Votre commande est actuellement en cours de préparation. + Ils seront bientôt expédiés ! +

+ @elseif($newStatus === 'shipped') + 🚚 Expédiée +

+ Bonne nouvelle ! Votre commande a été expédiée et est en route vers vous. +

+ @elseif($newStatus === 'delivered') + ✓ Livrée +

+ Votre commande a été livrée avec succès ! + Nous espérons que vous êtes satisfait de votre achat. +

+ @endif + +
+ + + + + + + + + + + + + + + + + + +
Numéro de commande{{ $order->order_code }}
Date de commande{{ $order->created_at->format('d/m/Y à H:i') }}
Montant total{{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA
Mode de livraison + @if($order->mode_livraison == 'standard') + Livraison Standard + @elseif($order->mode_livraison == 'express') + Livraison Express + @else + Retrait en magasin + @endif +
+
+ + + +

+ Si vous avez des questions concernant votre commande, + n'hésitez pas à nous contacter. +

+ +

+ Cordialement,
+ L'équipe Smalland +

+
+ + +
+ + \ No newline at end of file diff --git a/resources/views/emails/reset-password.blade.php b/resources/views/emails/reset-password.blade.php new file mode 100644 index 0000000..db2dd4c --- /dev/null +++ b/resources/views/emails/reset-password.blade.php @@ -0,0 +1,53 @@ + + + + + Réinitialisation du mot de passe + + + +
+ +

Bonjour {{ $user->prenom ?? $user->name }},

+

Vous avez demandé la réinitialisation de votre mot de passe pour {{ config('app.name') }}.

+

Cliquez sur le bouton ci-dessous pour définir un nouveau mot de passe :

+ Réinitialiser mon mot de passe +

Si vous n’avez pas demandé cette réinitialisation, ignorez simplement ce message.

+

À bientôt,
L’équipe {{ config('app.name') }}

+
+ + diff --git a/resources/views/emails/verify-email.blade.php b/resources/views/emails/verify-email.blade.php new file mode 100644 index 0000000..068b9f8 --- /dev/null +++ b/resources/views/emails/verify-email.blade.php @@ -0,0 +1,58 @@ + + + + + Vérification de votre e-mail + + + +
+ + + +

Bonjour {{ $user->prenom ?? $user->name }},

+

Merci de vous être inscrit sur {{ config('app.name') }} !

+

Veuillez confirmer votre adresse e-mail en cliquant sur le bouton ci-dessous :

+ + + Vérifier mon e-mail + +

Si vous n’avez pas créé de compte, ignorez simplement ce message.

+

À bientôt,
L’équipe {{ config('app.name') }}

+
+ + diff --git a/resources/views/formations/avis.blade.php b/resources/views/formations/avis.blade.php new file mode 100644 index 0000000..114a7c5 --- /dev/null +++ b/resources/views/formations/avis.blade.php @@ -0,0 +1,160 @@ +@extends('master') + +@section('content') +
+ + +
+
+
+
+
+
+ +
+
+

Avis Formation

+

{{ $formation->titre }}

+
+
+
+
+ @php $avgNote = $avis->avg('note') ?? 0; @endphp + @for ($i = 1; $i <= 5; $i++) + @if ($i <= $avgNote) + + @elseif ($i - 0.5 <= $avgNote) + + @else + + @endif + @endfor +
+ {{ number_format($avgNote, 1) }} · {{ $avis->total() }} avis +
+
+ + Retour + +
+
+
+ + +
+
+ @forelse ($avis as $a) + @php + $initials = strtoupper(substr($a->user->prenom, 0, 1) . substr($a->user->nom, 0, 1)); + $colors = ['primary', 'success', 'warning', 'danger', 'info']; + $color = $colors[array_rand($colors)]; + @endphp + + +
+
+ +
+
+ {{ $initials }} +
+
+
+
+
{{ $a->user->prenom }} {{ $a->user->nom }}
+
+ @for ($i = 1; $i <= 5; $i++) + @if ($i <= $a->note) + + @elseif ($i - 0.5 <= $a->note) + + @else + + @endif + @endfor + {{ number_format($a->note, 1) }}/5 +
+
+ + + {{ $a->created_at->diffForHumans() }} + +
+
+
+ + +
+

+ {{ $a->content_avis }} +

+
+ + +
+
+ + @empty + +
+
+ +

Aucun avis pour le moment

+

+ Soyez le premier à partager votre expérience sur cette formation. +

+ + Écrire un avis + +
+
+ @endforelse +
+
+ + + @if ($avis->hasPages()) +
+
+ +
+
+ @endif +
+ + +@endsection diff --git a/resources/views/formations/index.blade.php b/resources/views/formations/index.blade.php index b8faac2..0bb6578 100644 --- a/resources/views/formations/index.blade.php +++ b/resources/views/formations/index.blade.php @@ -1,141 +1,126 @@ -@extends('layouts.Appindex') - -@section('content') - -
-
-

Mes Formations

- - @if ($formations->count() > 0) -
- @foreach ($formations as $formation) -
- {{ $formation->titre }} -
-
-

{{ $formation->titre }}

- - {{ ucfirst($formation->niveau) }} - -
- -

{{ $formation->description }}

- -
-
- Progression - {{ $formation->calculated_progress }}% -
-
-
-
-
- - -
- {{ $formation->price }} € - - - Continuer - - - @php - $userAvis = \App\Models\Avis::where('formation_id', $formation->id) - ->where('user_id', auth()->id()) - ->first(); - @endphp +@if ($formations->count() > 0) +
+ @foreach ($formations as $formation) +
+ {{ $formation->titre }} +
+
+

{{ $formation->titre }}

+ + {{ ucfirst($formation->niveau) }} + +
- @if ($userAvis) - - Modifier mon avis - +

{{ $formation->description }}

-
- @csrf - @method('DELETE') - -
- @else - - - @endif -
-
+ {{-- + progession
+
+ Progression + {{ $formation->calculated_progress }}%
- - - - @endforeach -
- @else -
-
- - - - + + @elseif ($formation->calculated_progress == 100) + + @endif
-

Aucune formation

-

Vous n'êtes inscrit à aucune formation pour le moment.

- @endif +
+ + + + @endforeach +
+@else +
+
+ + + + +
+

Aucune formation

+

Vous n'êtes inscrit à aucune formation pour le moment.

-@endsection +@endif diff --git a/resources/views/formations/show.blade.php b/resources/views/formations/show.blade.php index 42c6867..1e5cb6a 100644 --- a/resources/views/formations/show.blade.php +++ b/resources/views/formations/show.blade.php @@ -1,104 +1,121 @@ -@extends('layouts.App') +@extends('Master') @section('content') -
-
- -
-
- -
- {{ $formation->titre }} -
- - -
-
-

{{ $formation->titre }}

- - {{ ucfirst($formation->niveau) }} - +
+
+
+
+ {{ $formation->titre }} +
+
+
+
+

{{ $formation->titre }}

+ {{ ucfirst($formation->niveau) }}
+

{{ $formation->description }}

-

{{ $formation->description }}

- -
-
- {{ $globalProgress }}% - - +
+
+ Progression globale + {{ number_format($globalProgress, 0) }}%
-
-
+
+
+ +
+ Statut : {{ $userFormation->status }} +
+ + + + @if ($globalProgress == 100) + + Télécharger mon certificat + +@else + + Attestation non disponible + +@endif + +
+
- - @if ($formation->objectifs->count() > 0) -
-

Objectifs de la formation

-
    + @if ($formation->objectifs->count() > 0) +
    +
    +

    Objectifs de la formation

    +
      @foreach ($formation->objectifs as $objectif) -
    • - - - - {{ $objectif->content }} +
    • + + {{ $objectif->content }}
    • @endforeach
    - @endif - - -
    -

    Plan de formation

    +
    + @endif -
    +
    +
    +

    Plan de formation

    +
    @foreach ($modulesWithProgress as $moduleData) -
    -
    -
    -

    - Module {{ $moduleData['module']->ordre }}: {{ $moduleData['module']->titre }} -

    - - {{ $moduleData['completed_lessons'] }}/{{ $moduleData['total_lessons'] }} leçons - -
    +
    +
    +
    + Module {{ $moduleData['module']->ordre }} : + {{ $moduleData['module']->titre }} +
    + + {{ $moduleData['completed_lessons'] }}/{{ $moduleData['total_lessons'] }} leçons + +
    -
    -
    - Progression du module - {{ round($moduleData['progress']) }}% -
    -
    -
    -
    +
    +
    + Progression du module + {{ round($moduleData['progress']) }}%
    - -
    -
    - @if ($moduleData['module']->quizz) - - Quiz disponible - - @endif -
    - - Accéder au module - +
    +
    + +
    + @if ($moduleData['module']->quizz) + Quiz disponible + @endif + Accéder au module +
    @endforeach
    @@ -106,5 +123,13 @@ class="bg-very-green text-white px-4 py-2 rounded-lg hover:bg-green-600 transiti
    + @endsection diff --git a/resources/views/layouts/about.blade.php b/resources/views/layouts/about.blade.php new file mode 100644 index 0000000..6bcbac8 --- /dev/null +++ b/resources/views/layouts/about.blade.php @@ -0,0 +1,452 @@ +@extends('master') + +@section('content') + + + + + + + +
    +
    +
    +
    + Small Land +
    +
    +

    Qui sommes-nous ?

    +

    + Small Land est une entreprise béninoise spécialisée dans la vente d'équipements agricoles + et de jardinage de qualité. +

    +

    + Fondée avec la passion de l'agriculture, de l'elevage et le désir d'accompagner les agriculteurs et des eleveurs. + Nous sommes une initiative portée par l'Établissement Sakoul Entreprises, spécialisée dans la fabrication d’incubateurs automatiques performants et adaptés aux besoins des éleveurs, la commercialisation d’œufs fertiles de volailles pour une reproduction de qualité, la fourniture d’équipements pour la fabrication de couveuses et autres matériels d’élevage et la mise à disposition d’équipements agricoles pour accompagner les producteurs dans la modernisation et la rentabilité de leurs activités. + +

    +

    + Notre mission est de rendre l'agriculture et l'elevage accessible et productive pour tous, en offrant + des équipements fiables à des prix compétitifs, tout en assurant un service client de qualité. +

    + +
    +
    +
    +
    + + +
    +
    +
    +

    Small Land en chiffres

    +
    +
    +
    +
    +
    + +
    +
    500+
    +

    Clients satisfaits

    +
    +
    +
    +
    +
    + +
    +
    200+
    +

    Produits disponibles

    +
    +
    +
    +
    +
    + +
    +
    1000+
    +

    Livraisons effectuées

    +
    +
    +
    +
    +
    + +
    +
    5/5
    +

    Note moyenne

    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Nos valeurs

    +

    Ce qui nous guide au quotidien

    +
    +
    +
    +
    +
    + +
    +

    Qualité

    +

    + Nous sélectionnons rigoureusement nos produits pour garantir leur durabilité + et leur efficacité. +

    +
    +
    +
    +
    +
    + +
    +

    Confiance

    +

    + Nous construisons des relations durables avec nos clients basées sur la transparence + et l'honnêteté. +

    +
    +
    +
    +
    +
    + +
    +

    Innovation

    +

    + Nous proposons constamment de nouvelles solutions pour améliorer la productivité agricole. +

    +
    +
    +
    +
    +
    + +
    +

    Service client

    +

    + Une équipe dédiée pour vous accompagner avant, pendant et après votre achat. +

    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Notre histoire

    +

    Le chemin parcouru depuis nos débuts

    +
    +
    +
    +
    +
    +

    2020 - La création

    +

    + Lancement de Small Land avec une vision claire : démocratiser l'accès + aux équipements agricoles de qualité au Bénin. +

    +
    +
    +
    +
    +
    +

    2021 - Premier succès

    +

    + Atteinte des 100 premiers clients et élargissement de notre catalogue + avec de nouvelles gammes de produits. +

    +
    +
    +
    +
    +
    +

    2023 - Expansion

    +

    + Ouverture de notre boutique en ligne et extension de nos services + de livraison à tout le pays. +

    +
    +
    +
    +
    +
    +

    2025 - Aujourd'hui

    +

    + Plus de 500 clients satisfaits, une boutique en ligne complète et + des formations pour accompagner nos clients. +

    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    Prêt à démarrer votre projet agricole ?

    +

    + Découvrez notre gamme complète de produits et bénéficiez de nos formations d'experts +

    +
    + +
    +
    +
    + +@endsection \ No newline at end of file diff --git a/resources/views/layouts/blog/blog-article.blade.php b/resources/views/layouts/blog/blog-article.blade.php index fec6d48..3282bba 100644 --- a/resources/views/layouts/blog/blog-article.blade.php +++ b/resources/views/layouts/blog/blog-article.blade.php @@ -1,394 +1,227 @@ @extends('master') -@section('content') - - -
    -
    -
    -
    - Permaculture -

    Introduction à la permaculture

    -

    Découvrez les principes de base de la permaculture et comment les appliquer dans votre jardin.

    - -
    -
    -
    -
    +@section('title', $article->titre) - +@section('content') + + + +
    -
    - -
    - -
    -

    La permaculture est bien plus qu'une simple méthode de jardinage. C'est une philosophie de conception qui s'inspire des écosystèmes naturels pour créer des environnements durables et productifs. Dans cet article, nous explorerons les principes fondamentaux de la permaculture et comment vous pouvez les appliquer dans votre propre jardin.

    - -

    Qu'est-ce que la permaculture ?

    -

    Le terme "permaculture" a été inventé dans les années 1970 par Bill Mollison et David Holmgren. Il combine les mots "permanent" et "agriculture" (ou "culture") pour décrire un système de conception visant à créer des habitats humains durables.

    - - Jardin en permaculture - -

    La permaculture repose sur trois éthiques fondamentales :

    -
      -
    • Prendre soin de la Terre : Préserver et régénérer les écosystèmes.
    • -
    • Prendre soin des personnes : Satisfaire les besoins fondamentaux de tous.
    • -
    • Partager équitablement : Redistribuer les surplus et limiter la consommation.
    • -
    - -

    Les principes de conception

    -

    David Holmgren a formulé 12 principes de permaculture qui servent de guide pour la conception de systèmes durables :

    - -
    - "La permaculture est la conception consciente de paysages qui miment les modèles et les relations observés dans la nature, visant à produire une abondance de nourriture, de fibres et d'énergie pour satisfaire les besoins locaux." -
    - -

    1. Observer et interagir

    -

    Prenez le temps d'observer votre environnement avant d'intervenir. Comprenez les patterns naturels, les flux d'énergie et les relations entre les éléments.

    - -

    2. Capter et stocker l'énergie

    -

    Développez des systèmes qui collectent les ressources quand elles sont abondantes (eau de pluie, énergie solaire) et les stockent pour une utilisation future.

    - -

    Application pratique au jardin

    -

    Voici quelques applications concrètes des principes de permaculture dans un jardin :

    - -

    Les buttes de culture

    -

    Les buttes permettent une meilleure gestion de l'eau, améliorent le drainage et augmentent la surface de culture. Elles créent également des microclimats favorables à certaines plantes.

    - -

    Le compagnonnage végétal

    -

    Associez des plantes qui s'entraident mutuellement. Par exemple, planter des œillets d'Inde près des tomates repousse les nématodes et autres parasites.

    - - Récupération d'eau de pluie +

    {{ $article->titre }}

    + +
    +
    + + +
    +
    + +
    +
    + + + @if($article->image_path) + {{ $article->titre }} + @endif + + +
    + {!! $article->content !!} +
    -

    Conclusion

    -

    La permaculture offre un cadre conceptuel puissant pour concevoir des systèmes résilients et productifs. En appliquant ses principes, vous pouvez transformer votre jardin en un écosystème diversifié qui nécessite moins d'entretien tout en produisant une abondance de nourriture.

    + + @if($article->tags) +
    +
    Tags :
    + @foreach(explode(',', $article->tags) as $tag) + {{ trim($tag) }} + @endforeach +
    + @endif -

    Commencez petit, observez les résultats, et ajustez votre conception au fil du temps. La permaculture est un processus d'apprentissage continu qui évolue avec votre compréhension de votre environnement.

    - + +
    + + Retour + - -
    -
    - + + +
    +
    - -
    -
    -

    À propos de l'auteur

    -
    -
    - Auteur -
    -
    -

    Marie Dupont

    -

    Agronome spécialisée en permaculture

    -

    Agriculteur bio depuis 15 ans, Marie a transformé sa ferme familiale en modèle de maraîchage bio intensif. Diplômée en agronomie, elle partage maintenant son expertise à travers des articles et des formations.

    - -
    -
    -
    -
    + +
    +

    Commentaires (3)

    - -
    -

    Commentaires (3)

    - -
    -
    -
    - Utilisateur -
    -
    Pierre Martin
    - 16 juin 2023 -
    -
    -

    Excellent article ! J'applique déjà certains principes dans mon jardin et les résultats sont impressionnants. Merci pour ces conseils.

    -
    -
    - -
    -
    -
    - Utilisateur -
    -
    Sophie Leroux
    - 17 juin 2023 -
    -
    -

    Je débute en permaculture et cet article m'a donné envie d'en apprendre davantage. Avez-vous des recommandations de livres pour approfondir le sujet ?

    -
    -
    - -
    -
    -
    - Utilisateur -
    -
    Thomas Dubois
    - 18 juin 2023 -
    -
    -

    Merci pour cet article très complet. Je cherchais justement des informations sur la conception de buttes de culture.

    -
    -
    +
    +
    Marie Dupont - 10 Oct 2025
    +

    Article très instructif ! J’ai adoré la clarté de l’explication.

    - -
    -

    Laisser un commentaire

    -
    -
    - - -
    -
    -
    - - -
    -
    - - -
    -
    - -
    +
    +
    Alexandre K. - 9 Oct 2025
    +

    Merci pour ce contenu, continuez à publier des articles comme celui-ci.

    -
    - -
    - - + + +@endsection diff --git a/resources/views/layouts/blog/blog-articles.blade.php b/resources/views/layouts/blog/blog-articles.blade.php new file mode 100644 index 0000000..6f5baa8 --- /dev/null +++ b/resources/views/layouts/blog/blog-articles.blade.php @@ -0,0 +1,31 @@ +@extends('master') + +@section('content') + + +
    +
    + @forelse($articles as $article) +
    +
    + {{ $article->titre }} +
    + {{ $article->category->name }} +
    {{ $article->titre }}
    +

    Par {{ $article->author }}

    +

    {{ Str::limit(strip_tags($article->content), 100) }}

    + Lire l’article +
    +
    +
    + @empty +

    Aucun article dans cette catégorie pour le moment.

    + @endforelse +
    +
    +@endsection diff --git a/resources/views/layouts/blog/blog-category.blade.php b/resources/views/layouts/blog/blog-category.blade.php index ca387ad..f5a466d 100644 --- a/resources/views/layouts/blog/blog-category.blade.php +++ b/resources/views/layouts/blog/blog-category.blade.php @@ -1,267 +1,41 @@ @extends('master') @section('content') - - -
-
- Produit -
Kit de jardinage débutant
-
-
39,90€139,90€
-
- Produit -
Coffret graines bio
-
-
24,90€249,80€
-
- Produit -
Arrosoir écologique
-
-
32,50€132,50€
+
+ @if($product->path_img) + {{ $product->nom }} + @endif +
{{ $product->nom }}
+
+
{{ number_format($product->prix, 0, ',', ' ') }} FCFA{{ $product->pivot->qte_commander }}{{ number_format($product->prix * $product->pivot->qte_commander, 0, ',', ' ') }} FCFA
Sous-total122,20€
Frais de livraison4,90€
Total127,10€{{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA
@@ -300,27 +169,8 @@
Adresse de livraison
- -
-
-
Adresse de livraison
-

- Marie Dupont
- 25 Rue des Jardins
- 75000 Paris
- France -

-
-
-
Adresse de facturation
-

- Marie Dupont
- 25 Rue des Jardins
- 75000 Paris
- France -

-
-
+

{{ $order->addresse }}

+

{{ $order->telephone }}

@@ -332,50 +182,49 @@
Suivi de livraison
-
+ +
- +
Commande confirmée
-

15 juin 2023, 14:30

-

Votre commande a été confirmée et est en préparation.

+

{{ $order->created_at->format('d/m/Y, H:i') }}

+

Votre commande a été reçue

-
+ +
- +
En préparation
-

15 juin 2023, 16:45

-

Votre commande est en cours de préparation.

+

Vos articles sont en cours de préparation

-
+ +
- +
Expédiée
-

16 juin 2023, 09:15

-

Votre commande a été expédiée.

+

Votre commande est en route

-
+ +
- +
-
En transit
-

16 juin 2023, 14:20

-

Votre colis est en cours de livraison.

+
Livrée
+

Commande livrée avec succès

-
-
- + @if($order->status == 'cancelled') +
+ + Commande annulée
-
Livrée
-

Estimation: 19 juin 2023

-

Votre colis sera livré à l'adresse indiquée.

-
+ @endif
@@ -384,48 +233,19 @@
-
Un problème avec votre commande ?
-

Notre équipe est là pour vous aider

- Contacter le support -
-
- - -
-
-
Historique des commandes
- -
-
-
CMD-123123
- 10 juin 2023 -
- Livrée -
- -
-
-
CMD-122987
- 5 juin 2023 -
- Livrée -
- -
-
-
CMD-122654
- 1 juin 2023 -
- Livrée -
- - +
Besoin d'aide ?
+

Notre équipe est là pour vous

+ Contacter le support
-
- -@endsection + @elseif(request('order_code')) +
+ + Aucune commande trouvée avec le code {{ request('order_code') }} +
+ @endif +
+ +@endsection \ No newline at end of file diff --git a/resources/views/layouts/boutique/orders/index.blade.php b/resources/views/layouts/boutique/orders/index.blade.php new file mode 100644 index 0000000..0b6a6cd --- /dev/null +++ b/resources/views/layouts/boutique/orders/index.blade.php @@ -0,0 +1,153 @@ +@extends('master') + +@section('content') + + + + + +
+ @if(session('success')) +
+ {{ session('success') }} + +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} + +
+ @endif + + @if($orders->count() > 0) +
+ @foreach($orders as $order) +
+
+
+
+
+
Commande #{{ $order->id }}
+ {{ $order->created_at->format('d/m/Y à H:i') }} +
+ + @switch($order->status) + @case('pending') En attente @break + @case('confirmed') Confirmée @break + @case('processing') En préparation @break + @case('shipped') Expédiée @break + @case('delivered') Livrée @break + @case('cancelled') Annulée @break + @default {{ $order->status }} + @endswitch + +
+ +
+

+ + {{ $order->products->count() }} article(s) +

+

+ + @if($order->mode_livraison == 'standard') + Livraison Standard + @elseif($order->mode_livraison == 'express') + Livraison Express + @else + Retrait en magasin + @endif +

+
+ +
+ {{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA + + Voir détails + +
+ + @if($order->status == 'pending') +
+ @csrf + +
+ @endif +
+
+
+ @endforeach +
+ + +
+ {{ $orders->links() }} +
+ @else +
+ +

Aucune commande pour le moment

+

Vous n'avez pas encore passé de commande.

+ + Découvrir nos produits + +
+ @endif +
+ +@endsection \ No newline at end of file diff --git a/resources/views/layouts/boutique/orders/show.blade.php b/resources/views/layouts/boutique/orders/show.blade.php new file mode 100644 index 0000000..f894d51 --- /dev/null +++ b/resources/views/layouts/boutique/orders/show.blade.php @@ -0,0 +1,301 @@ +@extends('master') + +@section('content') + + + + + +
+ @if(session('success')) +
+ {{ session('success') }} + +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} + +
+ @endif + +
+ +
+ +
+
+

Articles commandés ({{ $order->products->count() }})

+ + @foreach($order->products as $product) +
+ {{ $product->nom }} +
+
{{ $product->nom }}
+

{{ Str::limit($product->description, 60) }}

+

+ Quantité : {{ $product->pivot->qte_commander }} +

+
+
+

{{ number_format($product->prix, 0, ',', ' ') }} FCFA

+ x {{ $product->pivot->qte_commander }} +
+
+ @endforeach + + +
+
+ Sous-total + {{ number_format($order->products->sum(fn($p) => $p->prix * $p->pivot->qte_commander), 0, ',', ' ') }} FCFA +
+
+ Frais de livraison + + @php + $subtotal = $order->products->sum(fn($p) => $p->prix * $p->pivot->qte_commander); + $shipping = $order->price_total_order - $subtotal; + @endphp + {{ number_format($shipping, 0, ',', ' ') }} FCFA + +
+
+
+ Total TTC + {{ number_format($order->price_total_order, 0, ',', ' ') }} FCFA +
+
+
+
+ + +
+
+

+ Informations de livraison +

+ +
+
+
Mode de livraison
+

+ @if($order->mode_livraison == 'standard') + Livraison Standard (3-5 jours) + @elseif($order->mode_livraison == 'express') + Livraison Express (24-48h) + @else + Retrait en magasin + @endif +

+
+
+
Téléphone
+

{{ $order->telephone }}

+
+
+
Adresse de livraison
+

{{ $order->addresse }}

+
+
+
+
+ + +
+ + Retour à mes commandes + + + @if($order->status == 'pending') +
+ @csrf + +
+ @endif +
+
+ + +
+
+
+
+ Suivi de commande +
+ +
+
+
+ +
+
Commande reçue
+ {{ $order->created_at->format('d/m/Y H:i') }} +
+ +
+
+ +
+
Commande confirmée
+ En attente de confirmation +
+ +
+
+ +
+
En préparation
+ Votre commande est en cours de préparation +
+ +
+
+ +
+
Expédiée
+ Votre commande est en route +
+ +
+
+ +
+
Livrée
+ Commande livrée +
+ + @if($order->status == 'cancelled') +
+ + Commande annulée +
+ @endif +
+
+
+ + +
+
+ +
Besoin d'aide ?
+

Notre équipe est là pour vous

+ Contacter le support +
+
+
+
+
+ +@endsection \ No newline at end of file diff --git a/resources/views/layouts/boutique/product-detail.blade.php b/resources/views/layouts/boutique/product-detail.blade.php index 323ed8f..8e858c6 100644 --- a/resources/views/layouts/boutique/product-detail.blade.php +++ b/resources/views/layouts/boutique/product-detail.blade.php @@ -2,557 +2,309 @@ @section('content') - - -
-
- - -
- -
-
- Kit de jardinage débutant -
-
-
- Vue 1 -
-
- Vue 2 -
-
- Vue 3 -
-
- Vue 4 -
-
-
- - -
- En stock -

Kit de jardinage débutant

-
- - - - - - 4.5 (35 avis) -
-
39,90€
-

Tout le nécessaire pour commencer votre potager et entretenir vos plantes avec des outils de qualité.

- -
- -
- - - -
-
- -
- - -
- -
-
- - Livraison gratuite -
-
- - Retours gratuits -
-
- - Paiement sécurisé -
-
-
-
-
-
+ + +
- + + + + + @if(session('success')) +
+ {{ session('success') }} + +
+ @endif + + @if(session('error')) +
+ {{ session('error') }} + +
+ @endif +
-
-
-
- - -
-
-

Description du produit

-

Notre kit de jardinage débutant a été spécialement conçu pour ceux qui souhaitent se lancer dans le jardinage sans se ruiner. Il contient tous les outils essentiels pour entretenir vos plantes, que vous ayez un grand jardin ou simplement quelques pots sur un balcon.

- -

Fabriqué à partir de matériaux durables et respectueux de l'environnement, ce kit vous accompagnera pendant de nombreuses années. Les manches en bois de hêtre sont ergonomiques et confortables, tandis que les têtes en acier inoxydable résistent à la rouille.

- -

Idéal pour :

-
    -
  • Les débutants en jardinage
  • -
  • Les petits espaces (balcons, terrasses)
  • -
  • Les cadeaux pour amateurs de plantes
  • -
  • Les activités jardinage avec enfants
  • -
- -

Avantages :

-
    -
  • Matériaux durables et écologiques
  • -
  • Design ergonomique pour un confort d'utilisation
  • -
  • Range-outil pratique inclus
  • -
  • Garantie 2 ans
  • -
-
- -
-

Caractéristiques techniques

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Contenu du kit1 transplantoir, 1 petite griffe, 1 cultivateur, 1 plantoir, 1 range-outil
MatériauxManches en hêtre, têtes en acier inoxydable
Dimensions30 x 20 x 8 cm (range-outil fermé)
Poids1,2 kg
CouleurNaturel (bois) et acier inoxydable
Garantie2 ans
EntretienNettoyage à l'eau après utilisation, séchage complet avant rangement
-
-
- -
-

Avis clients

- -
-
-
4.5
-
- - - - - -
-
35 avis
-
-
-
-
- 5 -
-
-
- 21 -
-
-
-
- 4 -
-
-
- 9 -
-
-
-
- 3 -
-
-
- 3 -
-
-
-
- 2 -
-
-
- 1 -
-
-
-
- 1 -
-
-
- 1 -
-
-
-
- - -
-
- Avatar -
-
Sophie L.
-
- - - - - -
-
-
-
Parfait pour débuter
-

J'ai acheté ce kit pour commencer mon petit potager sur mon balcon. Les outils sont de très bonne qualité, ergonomiques et solides. Le range-outil est très pratique pour garder tout organisé. Je recommande !

- Publié il y a 2 semaines -
- - -
-
- Avatar -
-
Pierre D.
-
- - - - - -
-
-
-
Bon rapport qualité-prix
-

Des outils solides et bien finis. Le manche en bois est agréable en main. Seul bémol : le range-outil est un peu juste pour ranger tous les outils, il faut les positionner précisément.

- Publié il y a 1 mois -
- - Voir tous les avis -
+ +
+
+ @if($product->path_img) + {{ $product->nom }} + @else +
+
-
+ @endif +
+
+ + +
+ + @if($product->qte > 10) + + En stock ({{ $product->qte }} disponibles) + + @elseif($product->qte > 0) + + Stock limité ({{ $product->qte }} restants) + + @else + + Rupture de stock + + @endif + + +

{{ $product->nom }}

+ + + @if($product->category) +

+ + + {{ $product->category->nom }} + +

+ @endif + + +
+ {{ number_format($product->prix, 0, ',', ' ') }} FCFA
- - -
-
-

Produits similaires

- -
-
-
- En stock - Arrosoir -
-
Arrosoir écologique
-

Fabriqué à partir de matériaux recyclés, capacité 10L.

-
- - - - - - (18) -
-
- 32,50€ - -
-
+ + +

{{ $product->description ?? 'Aucune description disponible.' }}

+ +
+ + + @auth + @if($product->qte > 0) +
+ @csrf +
+ +
+ + +
- -
-
- En stock - Graines bio -
-
Coffret graines bio
-

15 variétés de légumes et herbes aromatiques biologiques.

-
- - - - - - (47) -
-
- 24,90€ - -
-
-
+ +
+
+ + @else +
+ + Ce produit est actuellement en rupture de stock.
+ @endif + @else +
+ + Veuillez vous connecter + pour ajouter ce produit au panier.
-
-
- - -
- -
-
-
Livraison et retours
- -
- -
-
Livraison standard
-

Gratuite à partir de 50€ d'achat
Délai : 2-3 jours ouvrés

-
-
- -
- -
-
Retours gratuits
-

30 jours pour changer d'avis
Retours simples et gratuits

-
-
- -
- -
-
Paiement sécurisé
-

Vos données bancaires sont cryptées

-
-
+ + @endauth + +
+ + +
+
+ +
+
+ Livraison gratuite +

Pour les commandes supérieures à 50 000 FCFA

- - -
-
- -
Garantie 2 ans
-

Ce produit est garanti 2 ans contre tout défaut de fabrication.

+ +
+
+ +
+
+ Retours gratuits +

Sous 14 jours

- - -
-
- -
Une question ?
-

Notre équipe est là pour vous aider

- Contactez-nous + +
+
+ +
+
+ Paiement sécurisé +

Transactions 100% sécurisées

+
+ +
+
+
+

+ Description détaillée +

+

{{ $product->description ?? 'Aucune description disponible.' }}

+
+
- - -@endsection + @if($product->category) + + Voir plus dans {{ $product->category->nom }} + + @endif +
+
+ + + +@endsection \ No newline at end of file diff --git a/resources/views/layouts/boutique/product-list.blade.php b/resources/views/layouts/boutique/product-list.blade.php index a578a1d..d598efb 100644 --- a/resources/views/layouts/boutique/product-list.blade.php +++ b/resources/views/layouts/boutique/product-list.blade.php @@ -2,344 +2,258 @@ @section('content') - - + + +