src/Controller/Admin/DashboardController.php line 97

  1. <?php
  2. namespace App\Controller\Admin;
  3. use App\Controller\Admin\PaidOrdersCrudController;
  4. use App\Controller\Admin\UnpaidOrdersCrudController;
  5. use App\Entity\Application;
  6. use App\Entity\CurrencyExchangeRate;
  7. use App\Entity\EntityLog;
  8. use App\Entity\ExportExcel;
  9. use App\Entity\FrontTheme;
  10. use App\Entity\Link;
  11. use App\Entity\LinkType;
  12. use App\Entity\LogHistory;
  13. use App\Entity\Notification;
  14. use App\Entity\Role;
  15. use App\Entity\Settings;
  16. use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
  17. use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
  18. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  19. use Symfony\Component\HttpFoundation\Response;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. use App\Entity\User;
  22. use App\Entity\WebsiteTheme;
  23. use App\IlaveU\ShopBundle\Entity\Order\Order;
  24. use App\IlaveU\ShopBundle\Entity\Product\Product;
  25. use App\IlaveU\ShopBundle\Entity\Store\Store;
  26. use App\IlaveU\ShopBundle\Entity\Vendor\Vendor;
  27. use App\IlaveU\ShopBundle\Service\IlaveUShopStatisticProvider;
  28. use App\Repository\ApplicationRepository;
  29. use App\Service\IlaveUSettingsProvider;
  30. use Doctrine\Persistence\ManagerRegistry;
  31. use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
  32. use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
  33. use Symfony\Component\Filesystem\Filesystem;
  34. use Symfony\Component\Finder\Finder;
  35. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  36. use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
  37. use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  38. use EasyCorp\Bundle\EasyAdminBundle\Contracts\Orm\EntityRepositoryInterface;
  39. use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
  40. use Symfony\Component\HttpFoundation\Request;
  41. use Symfony\Component\HttpFoundation\RequestStack;
  42. class DashboardController extends AbstractDashboardController
  43. {
  44.     public function __construct(
  45.         private readonly ManagerRegistry $doctrine,
  46.         private readonly IlaveUShopStatisticProvider $ilaveShopStatisticProvider,
  47.         private readonly RequestStack $requestStack,
  48.     ) {}
  49.     #[Route(path'/admin'name'admin_non_locale')]
  50.     public function indexNonLocale(Request $request): Response
  51.     {
  52.         return $this->redirectToRoute("admin", ["_locale" => $request->getLocale()]);
  53.     }
  54.     #[Route('/{_locale}/admin/website-theme/grapesjs_edit'name'website_theme_grapesjs_edit')]
  55.     public function website_theme_grapesjs_edit(): Response
  56.     {
  57.         return $this->render('@IlaveU/FrontBundle/Themes/' $this->container->get('twig')->getGlobals()["settings"]->get()->getAssetFolderName() . '/templates/admin/website-theme/grapesjs.html.twig');
  58.     }
  59.     #[Route(path'/{_locale}/admin/apps-store'name'app_store')]
  60.     public function appStore(Request $requestApplicationRepository $applicationRepositoryIlaveUSettingsProvider $ilaveSettingsProvider): Response
  61.     {
  62.         $appsArray = [];
  63.         foreach ($applicationRepository->findAll() as $singleApp) {
  64.             $appsArray[] = [
  65.                 "id" => $singleApp->getId(),
  66.                 "name" => $singleApp->getName(),
  67.                 "image" => $singleApp->getImage(),
  68.                 "price" => $singleApp->getPrice(),
  69.                 "pageUrl" => $singleApp->getPageUrl(),
  70.             ];
  71.         }
  72.         return $this->render("admin/app-store.html.twig", [
  73.             "installedApps" => $appsArray,
  74.             "JWT" => $ilaveSettingsProvider->createJWTForUser($this->getUser())
  75.         ]);
  76.     }
  77.     #[Route(path'/{_locale}/admin'name'admin')]
  78.     public function index(): Response
  79.     {
  80.         $url $this->requestStack->getCurrentRequest()->server->get('REQUEST_URI');
  81.         
  82.         $parsedUrl parse_url($url);
  83.         parse_str($parsedUrl['query'] ?? ''$queryParams);
  84.         if (count($queryParams) == 0) {
  85.             $queryParams['store'] = null;
  86.             $queryParams['range'] = null;
  87.             $queryParams['from'] = null;
  88.             $queryParams['to'] = null;
  89.         }
  90.         $range   $queryParams['range'] ?? 'today';
  91.         $from    $queryParams['from'] ?? null;
  92.         $to      $queryParams['to'] ?? null;
  93.         $storeParam   = (int)$queryParams['store'] ?? null;
  94.         $store null;
  95.         
  96.         
  97.         // Check if user has ROLE_STORE and get their store
  98.         if ($this->isGranted('ROLE_STORE')) {
  99.             $currentUser $this->getUser();
  100.             if ($currentUser) {
  101.                 $storeEntity $this->doctrine->getRepository(Store::class)->findOneBy(["user" => $currentUser]);
  102.                 $store $storeEntity;
  103.             }
  104.         } elseif ($storeParam != 0) {
  105.             $storeEntity $this->doctrine->getRepository(Store::class)->findOneBy(["id" => $storeParam]);
  106.             $store $storeEntity;
  107.         }
  108.         
  109.         
  110.         //dd($request);
  111.         // Get list of stores
  112.         $stores $this->doctrine->getRepository(Store::class)->findAll();
  113.         // Handle predefined ranges
  114.         $now = new \DateTimeImmutable();
  115.         // If custom date range provided, it overrides predefined range
  116.         if ($from && $to) {
  117.             $start \DateTimeImmutable::createFromFormat('Y-m-d'$from)?->setTime(00);
  118.             $end   \DateTimeImmutable::createFromFormat('Y-m-d'$to)?->setTime(235959);
  119.         } elseif ($range === 'today') {
  120.             $start $now->modify('today')->setTime(00);
  121.             $end $now->modify('today')->setTime(235959);
  122.         } elseif ($range === 'yesterday') {
  123.             $start $now->modify('yesterday')->setTime(00);
  124.             $end $now->modify('yesterday')->setTime(235959);
  125.         } elseif ($range === 'week') {
  126.             $start $now->modify('this week')->setTime(00);
  127.             $end $now->modify('next week')->setTime(00)->modify('-1 second');
  128.         } elseif ($range === 'month') {
  129.             $start $now->modify('first day of this month')->setTime(00);
  130.             $end $now->modify('last day of this month')->setTime(235959);
  131.         } elseif ($range === 'year') {
  132.             $start $now->modify('first day of January')->setTime(00);
  133.             $end $now->modify('last day of December')->setTime(235959);
  134.         } else { // Month By Default
  135.             $start $now->modify('today')->setTime(00);
  136.             $end $now->modify('today')->setTime(235959);
  137.         }
  138.         // If custom date range provided (overrides predefined range)
  139.         
  140.         $validOrders $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  141.             ->select('count(orderAlias.id)')
  142.             ->andWhere("orderAlias.status <> 'draft'")
  143.             ->andWhere("orderAlias.status <> 'cancelled'")
  144.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  145.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  146.             ->setParameter('start'$start)
  147.             ->setParameter('end'$end);
  148.             if($store){
  149.                 $validOrders->andWhere("orderAlias.store = :store");
  150.                 $validOrders->setParameter('store'$store);
  151.             }
  152.             $validOrders $validOrders->getQuery()->getSingleScalarResult();
  153.         $cancelledOrders $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  154.             ->select('count(orderAlias.id)')
  155.             ->andWhere("orderAlias.status = 'cancelled' OR orderAlias.statusShipping = 'annulee'")
  156.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  157.             ->setParameter('start'$start)
  158.             ->setParameter('end'$end);
  159.             if($store){
  160.                 $cancelledOrders->andWhere("orderAlias.store = :store");
  161.                 $cancelledOrders->setParameter('store'$store);
  162.             }
  163.             $cancelledOrders $cancelledOrders->getQuery()->getSingleScalarResult();
  164.         $shippedOrders $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  165.             ->select('count(orderAlias.id)')
  166.             ->andWhere("orderAlias.statusShipping = 'shipped'")
  167.             ->andWhere("orderAlias.status <> 'draft'")
  168.             ->andWhere("orderAlias.status <> 'cancelled'")
  169.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  170.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  171.             ->setParameter('start'$start)
  172.             ->setParameter('end'$end);
  173.             if($store){
  174.                 $shippedOrders->andWhere("orderAlias.store = :store");
  175.                 $shippedOrders->setParameter('store'$store);
  176.             }
  177.             $shippedOrders $shippedOrders->getQuery()->getSingleScalarResult();
  178.         $soldProducts =   $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  179.             ->select('sum(orderItems.quantity)')
  180.             ->leftJoin("orderAlias.orderItems""orderItems")
  181.             ->andWhere("orderAlias.status <> 'draft'")
  182.             ->andWhere("orderAlias.status <> 'cancelled'")
  183.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  184.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  185.             ->setParameter('start'$start)
  186.             ->setParameter('end'$end);
  187.             if($store){
  188.                 $soldProducts->andWhere("orderAlias.store = :store");
  189.                 $soldProducts->setParameter('store'$store);
  190.             }
  191.             $soldProducts $soldProducts->getQuery()->getSingleScalarResult();
  192.         $revenuedProducts =   $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  193.             ->select('sum(orderItems.quantity * orderItems.price)')
  194.             ->leftJoin("orderAlias.orderItems""orderItems")
  195.             ->andWhere("orderAlias.status <> 'draft'")
  196.             ->andWhere("orderAlias.status <> 'cancelled'")
  197.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  198.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  199.             ->setParameter('start'$start)
  200.             ->setParameter('end'$end);
  201.             if($store){
  202.                 $revenuedProducts->andWhere("orderAlias.store = :store");
  203.                 $revenuedProducts->setParameter('store'$store);
  204.             }
  205.             $revenuedProducts $revenuedProducts->getQuery()->getSingleScalarResult();
  206.         $averageOrderAmount $validOrders $revenuedProducts $validOrders 0;
  207.         //$inTypes = Order::IN_TYPES;
  208.         //$outTypes = Order::OUT_TYPES;
  209.         // $productInStock = $this->doctrine->createQueryBuilder()
  210.         //     ->select('p.id AS product_id, p.name AS product_name,p.initialStock AS initial_stock')
  211.         //     ->addSelect('
  212.         //         SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) AS stock_in,
  213.         //         SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END) AS stock_out,
  214.         //         (
  215.         //             p.initialStock +
  216.         //             SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) -
  217.         //             SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END)
  218.         //         ) AS current_stock
  219.         //     ')
  220.         //     ->from(Product::class, 'p')
  221.         //     ->leftJoin('p.orderItems', 'oi')
  222.         //     ->leftJoin('oi.parentOrder', 'o')
  223.         //     ->groupBy('p.id')
  224.         //     ->having('
  225.         //         (
  226.         //             p.initialStock +
  227.         //             SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) -
  228.         //             SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END)
  229.         //         ) > 0
  230.         //     ')
  231.         //     ->andWhere("o.status = '".Order::STATUS_VALIDATED."'")
  232.         //     //->andWhere('o.createdAt BETWEEN :start AND :end')
  233.         //     // ->setParameter('start', $start)
  234.         //     // ->setParameter('end', $end)
  235.         //     ->setMaxResults(16)
  236.         //     ->orderBy('current_stock', 'DESC')
  237.         //     ->getQuery()
  238.         //     ->getArrayResult();
  239.         $mostOrderedCategory $this->doctrine->getRepository(Order::class)
  240.             ->createQueryBuilder('o')
  241.             ->select('c.id, c.name as categoryName, COUNT(DISTINCT o.id) as orderCount')
  242.             ->join('o.orderItems''oi')
  243.             ->join('oi.product''p')
  244.             ->join('p.categoriesProduct''c'// Assuming products have a many-to-many relationship with categories
  245.             ->where("o.status NOT IN ('draft', 'cancelled') AND o.statusShipping <> 'annulee'")
  246.             ->andWhere('o.createdAt BETWEEN :start AND :end')
  247.             ->groupBy('c.id, c.name')
  248.             ->orderBy('orderCount''DESC')
  249.             ->setMaxResults(1// Get only the top category
  250.             ->setParameter('start'$start)
  251.             ->setParameter('end'$end);
  252.             if($store){
  253.                 $mostOrderedCategory->andWhere("o.store = :store");
  254.                 $mostOrderedCategory->setParameter('store'$store);
  255.             }
  256.             $mostOrderedCategory->getQuery()->getOneOrNullResult();
  257.             $mostOrderedCategory $mostOrderedCategory->getQuery()->getOneOrNullResult();
  258.                     
  259.         $mostOrderedCategoryName $mostOrderedCategory $mostOrderedCategory['categoryName'] : 'N/A';
  260.         // Get the most ordered product
  261.         $mostOrderedProduct $this->doctrine->getRepository(Order::class)
  262.             ->createQueryBuilder('o')
  263.             ->select('p.id, p.name as productName, SUM(oi.quantity) as totalQuantity, COUNT(DISTINCT o.id) as orderCount')
  264.             ->join('o.orderItems''oi')
  265.             ->join('oi.product''p')
  266.             ->where("o.status NOT IN ('draft', 'cancelled') AND o.statusShipping <> 'annulee'")
  267.             ->andWhere('o.createdAt BETWEEN :start AND :end')
  268.             ->groupBy('p.id, p.name')
  269.             ->orderBy('totalQuantity''DESC')
  270.             ->setMaxResults(1)
  271.             ->setParameter('start'$start)
  272.             ->setParameter('end'$end);
  273.             if($store){
  274.                 $mostOrderedProduct->andWhere("o.store = :store");
  275.                 $mostOrderedProduct->setParameter('store'$store);
  276.             }
  277.             $mostOrderedProduct $mostOrderedProduct->getQuery()->getOneOrNullResult();
  278.             
  279.         $mostOrderedProductName $mostOrderedProduct $mostOrderedProduct['productName'] : 'N/A';
  280.         // Payment status stats - separate queries for different payment statuses
  281.         // Paid orders: filter by paidAt date
  282.         $paidOrdersQuery $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  283.             ->select('count(orderAlias.id)')
  284.             ->andWhere("orderAlias.status <> 'draft'")
  285.             ->andWhere("orderAlias.status <> 'cancelled'")
  286.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  287.             ->andWhere('orderAlias.paidAt BETWEEN :start AND :end')
  288.             ->setParameter('start'$start)
  289.             ->setParameter('end'$end);
  290.         if($store){
  291.             $paidOrdersQuery->andWhere("orderAlias.store = :store");
  292.             $paidOrdersQuery->setParameter('store'$store);
  293.         }
  294.         $paidOrders $paidOrdersQuery->getQuery()->getSingleScalarResult();
  295.         // Unpaid orders: filter by same date range as other stats
  296.         $unpaidOrdersQuery $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  297.             ->select('count(orderAlias.id)')
  298.             ->andWhere("orderAlias.status <> 'draft'")
  299.             ->andWhere("orderAlias.status <> 'cancelled'")
  300.             ->andWhere("orderAlias.statusShipping = 'livree'")
  301.             ->andWhere('orderAlias.payedAmount = 0')
  302.             ->andWhere("orderAlias.status NOT IN ('paid', 'partially-paid')")
  303.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  304.             ->setParameter('start'$start)
  305.             ->setParameter('end'$end);
  306.         if($store){
  307.             $unpaidOrdersQuery->andWhere("orderAlias.store = :store");
  308.             $unpaidOrdersQuery->setParameter('store'$store);
  309.         }
  310.         $unpaidOrders $unpaidOrdersQuery->getQuery()->getSingleScalarResult();
  311.         // Calculate total amounts for paid orders (filtered by paidAt date)
  312.         $paidOrdersAmountQuery $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  313.             ->select('sum(orderAlias.payedAmount)')
  314.             ->andWhere("orderAlias.status <> 'draft'")
  315.             ->andWhere("orderAlias.status <> 'cancelled'")
  316.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  317.             ->andWhere('orderAlias.paidAt BETWEEN :start AND :end')
  318.             ->setParameter('start'$start)
  319.             ->setParameter('end'$end);
  320.         if($store){
  321.             $paidOrdersAmountQuery->andWhere("orderAlias.store = :store");
  322.             $paidOrdersAmountQuery->setParameter('store'$store);
  323.         }
  324.         $paidOrdersAmount $paidOrdersAmountQuery->getQuery()->getSingleScalarResult() ?: 0;
  325.         // Calculate total amounts for unpaid orders (filtered by same date range)
  326.         $unpaidOrdersAmountQuery $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  327.             ->select('sum(orderItems.quantity * orderItems.price)')
  328.             ->leftJoin("orderAlias.orderItems""orderItems")
  329.             ->andWhere("orderAlias.status <> 'draft'")
  330.             ->andWhere("orderAlias.status <> 'cancelled'")
  331.             ->andWhere("orderAlias.statusShipping = 'livree'")
  332.             ->andWhere('orderAlias.payedAmount = 0')
  333.             ->andWhere("orderAlias.status NOT IN ('paid', 'partially-paid')")
  334.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  335.             ->setParameter('start'$start)
  336.             ->setParameter('end'$end);
  337.         if($store){
  338.             $unpaidOrdersAmountQuery->andWhere("orderAlias.store = :store");
  339.             $unpaidOrdersAmountQuery->setParameter('store'$store);
  340.         }
  341.         $unpaidOrdersAmount $unpaidOrdersAmountQuery->getQuery()->getSingleScalarResult() ?: 0;
  342.         // Breakdown: paid amounts grouped by payment method
  343.         $paidByMethodQB $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  344.             ->select('pm.name AS methodName, pm.code AS methodCode, SUM(orderAlias.payedAmount) AS totalAmount')
  345.             ->leftJoin('orderAlias.paymentMethod''pm')
  346.             ->andWhere("orderAlias.status <> 'draft'")
  347.             ->andWhere("orderAlias.status <> 'cancelled'")
  348.             ->andWhere("orderAlias.statusShipping <> 'annulee'")
  349.             ->andWhere('orderAlias.paidAt BETWEEN :start AND :end')
  350.             ->groupBy('pm.id, pm.name, pm.code')
  351.             ->setParameter('start'$start)
  352.             ->setParameter('end'$end);
  353.         if ($store) {
  354.             $paidByMethodQB->andWhere('orderAlias.store = :store')->setParameter('store'$store);
  355.         }
  356.         $paidByMethod $paidByMethodQB->getQuery()->getArrayResult();
  357.         // Breakdown: unpaid amounts grouped by payment method
  358.         $unpaidByMethodQB $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
  359.             ->select('pm.name AS methodName, pm.code AS methodCode, SUM(orderItems.quantity * orderItems.price) AS totalAmount')
  360.             ->leftJoin('orderAlias.paymentMethod''pm')
  361.             ->leftJoin('orderAlias.orderItems''orderItems')
  362.             ->andWhere("orderAlias.status <> 'draft'")
  363.             ->andWhere("orderAlias.status <> 'cancelled'")
  364.             ->andWhere("orderAlias.statusShipping = 'livree'")
  365.             ->andWhere('orderAlias.payedAmount = 0')
  366.             ->andWhere("orderAlias.status NOT IN ('paid', 'partially-paid')")
  367.             ->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
  368.             ->groupBy('pm.id, pm.name, pm.code')
  369.             ->setParameter('start'$start)
  370.             ->setParameter('end'$end);
  371.         if ($store) {
  372.             $unpaidByMethodQB->andWhere('orderAlias.store = :store')->setParameter('store'$store);
  373.         }
  374.         $unpaidByMethod $unpaidByMethodQB->getQuery()->getArrayResult();
  375.         $stats = [
  376.             [
  377.                 'label' => 'Chiffre d\'affaires',
  378.                 'value' => (float)round($revenuedProducts2),
  379.                 'icon' => 'fa-coins',
  380.                 'cssClass' => 'stat-revenue',
  381.                 'description' => 'Revenu total généré par les ventes',
  382.                 'url' => null,
  383.                 'unit' => 'MAD',
  384.             ],
  385.             [
  386.                 'label' => 'Ventes',
  387.                 'value' => (int)$validOrders,
  388.                 'icon' => 'fa-calendar-check',
  389.                 'cssClass' => 'stat-monthly-orders',
  390.                 'description' => 'Nombre de ventes validées',
  391.                 'url' => null,
  392.                 'unit' => null,
  393.             ],
  394.             [
  395.                 'label' => 'Produits vendus',
  396.                 'value' => (int)$soldProducts,
  397.                 'icon' => 'fa-boxes',
  398.                 'cssClass' => 'stat-products-sold',
  399.                 'description' => 'Nombre total de produits vendus',
  400.                 'url' => null,
  401.                 'unit' => null,
  402.             ],
  403.             [
  404.                 'label' => 'Panier moyen',
  405.                 'value' => (float)round($averageOrderAmount2),
  406.                 'icon' => 'fa-shopping-basket',
  407.                 'cssClass' => 'stat-average-basket',
  408.                 'description' => 'Montant moyen dépensé par commande',
  409.                 'url' => null,
  410.                 'unit' => 'MAD',
  411.             ],
  412.             [
  413.                 'label' => 'Commandes annulées',
  414.                 'value' => (float)$cancelledOrders,
  415.                 'icon' => 'fa-undo',
  416.                 'cssClass' => 'stat-refunds',
  417.                 'description' => 'Nombre de commandes annulées',
  418.                 'url' => null,
  419.                 'unit' => null,
  420.             ],
  421.             [
  422.                 'label' => 'Commandes livrées',
  423.                 'value' => (float)$shippedOrders,
  424.                 'icon' => 'fa-truck',
  425.                 'cssClass' => 'stat-delivered-orders',
  426.                 'description' => 'Nombre de commandes livrées',
  427.                 'url' => null,
  428.                 'unit' => null,
  429.             ],
  430.             [
  431.                 'label' => 'Catégorie phare',
  432.                 'value' => $mostOrderedCategoryName,
  433.                 'icon' => 'fa-star',
  434.                 'cssClass' => 'stat-top-category',
  435.                 'description' => 'Catégorie la plus vendue ce mois-ci',
  436.                 'url' => null,
  437.                 'unit' => null,
  438.             ],
  439.             [
  440.                 'label' => 'Produit phare',
  441.                 'value' => $mostOrderedProductName,
  442.                 'icon' => 'fa-star',
  443.                 'cssClass' => 'stat-top-product',
  444.                 'description' => 'Produit la plus vendu ce mois-ci',
  445.                 'url' => null,
  446.                 'unit' => null,
  447.             ],
  448.             [
  449.                 'label' => 'Commandes réglées',
  450.                 'value' => $paidOrders,
  451.                 'amount' => (float)$paidOrdersAmount,
  452.                 'icon' => 'fa-check-circle',
  453.                 'cssClass' => 'stat-paid-orders',
  454.                 'description' => 'Nombre de commandes entièrement réglées',
  455.                 'url' => null,
  456.                 'unit' => null,
  457.                 'breakdown' => array_map(function(array $row) { return ['label' => $row['methodName'] ?? '—''amount' => (float)($row['totalAmount'] ?? 0)]; }, $paidByMethod),
  458.             ],
  459.             [
  460.                 'label' => 'Commandes à régler',
  461.                 'value' => $unpaidOrders,
  462.                 'amount' => (float)$unpaidOrdersAmount,
  463.                 'icon' => 'fa-clock',
  464.                 'cssClass' => 'stat-unpaid-orders',
  465.                 'description' => 'Nombre de commandes en attente de réglement',
  466.                 'url' => null,
  467.                 'unit' => null,
  468.                 'breakdown' => array_map(function(array $row) { return ['label' => $row['methodName'] ?? '—''amount' => (float)($row['totalAmount'] ?? 0)]; }, $unpaidByMethod),
  469.             ],
  470.         ];
  471.         $productLabels = [];
  472.         $productChartData = [];
  473.         // foreach ($productInStock as $product) {
  474.         //     $productLabels[] = $product['product_name'];
  475.         //     $productChartData[] = (float) $product['current_stock']; // cast to float for Chart.js
  476.         // }
  477.         // $productChartData = [
  478.         //     'labels' => $productLabels,
  479.         //     'data' => $productChartData,
  480.         // ];
  481.         return $this->render(
  482.             "bundles/EasyAdminBundle/welcome.html.twig",
  483.             [
  484.                 'stores' => $stores,
  485.                 'store' => $store,
  486.                 'stats' => $stats,
  487.                 'productChartData' => $productChartData,
  488.                 'queryParams' => $queryParams,
  489.             ]
  490.         );
  491.     }
  492.     public function configureCrud(): Crud
  493.     {
  494.         return
  495.             Crud::new()
  496.             ->setDefaultSort(["id" => 'DESC'])
  497.             ->setPaginatorPageSize(12)
  498.         ;
  499.     }
  500.     public function configureDashboard(): Dashboard
  501.     {
  502.         $mainSettings $this->doctrine->getManager()->getRepository(Settings::class)->findOneBy(["code" => "main"]);
  503.         $nameSpaceTrans strtolower($mainSettings->getProjectName()) . "-admin";
  504.         $urlImage "../themes/" strtolower($mainSettings->getAssetFolderName()) . "/admin/images/logo.png";
  505.         return Dashboard::new()
  506.             ->setTitle('<img title="Dashboard" src="' $urlImage '" />
  507.             
  508.             ')
  509.             //->renderContentMaximized()
  510.             ->setFaviconPath("../themes/" strtolower($mainSettings->getAssetFolderName()) . "/admin/images/icon.png")
  511.             ->setTranslationDomain($nameSpaceTrans)
  512.             ->disableUrlSignatures()
  513.             ->setLocales(
  514.                 [
  515.                     'fr' => 'Français',
  516.                     'en' => 'English',
  517.                     'ar' => 'Arabe',
  518.                 ]
  519.             )
  520.         ;
  521.     }
  522.     public function configureMenuItems(): iterable
  523.     {
  524.         /* START : Les Extensions IlaveU */
  525.         $applications $this->doctrine->getManager()->getRepository(Application::class)->findBy(["isEnabled" => true], ["menuOrder" => "ASC"]);
  526.         $settings $this->doctrine->getManager()->getRepository(Settings::class)->findOneBy(["code" => "main"]);
  527.         //$finder = new Finder();
  528.         $filesystem = new Filesystem();
  529.         //$finder->directories()->in(__DIR__."/../../IlaveU")->depth('== 0');
  530.         // For Principal Bundles (ShopBundle + FrontBundle + ...) 
  531.         foreach ($applications as $singleApplication) {
  532.             $bundleExist $filesystem->exists(__DIR__ "/../../IlaveU/" $singleApplication->getName() . "/IlaveU" $singleApplication->getName() . ".php");
  533.             if (!$bundleExist) {
  534.                 continue;
  535.             }
  536.             $bundleName $singleApplication->getName();
  537.             // Les themes systemes IlaveU (FrontBundle Themes)
  538.             if ($bundleName == "FrontBundle") {
  539.                 $bundleDashboardController 'App\IlaveU\FrontBundle\Themes\\' $settings->getFrontTheme() . '\Controller\DashboardController';
  540.             } else {
  541.                 $bundleDashboardController 'App\IlaveU\\' $bundleName '\Controller\DashboardController';
  542.             }
  543.             $dashboard = new $bundleDashboardController();
  544.             foreach ($dashboard->configureMenuItems() as $menu) {
  545.                 yield $menu;
  546.             }
  547.         }
  548.         // For Additional Apps Bundles (POSBundle + OtherBundle + ...)
  549.         foreach ($applications as $singleApplication) {
  550.             if ($singleApplication->getParentApplication()) {
  551.                 continue;
  552.             }
  553.             $menuArray = [];
  554.             $bundleExist $filesystem->exists(__DIR__ "/../../IlaveU/Apps/" $singleApplication->getName() . "/IlaveU" $singleApplication->getName() . ".php");
  555.             if (!$bundleExist) {
  556.                 continue;
  557.             }
  558.             $bundleName $singleApplication->getName();
  559.             $bundleDashboardController 'App\IlaveU\Apps\\' $bundleName '\Controller\DashboardController';
  560.             $dashboard = new $bundleDashboardController();
  561.             foreach ($dashboard->configureMenuItems() as $menu) {
  562.                 yield $menu;
  563.             }
  564.             //SubApplications 
  565.             foreach ($singleApplication->getSubApplications() as $subApplication) {
  566.                 $bundleExist $filesystem->exists(__DIR__ "/../../IlaveU/Apps/" $subApplication->getName() . "/IlaveU" $subApplication->getName() . ".php");
  567.                 if (!$bundleExist) {
  568.                     continue;
  569.                 }
  570.                 $bundleName $subApplication->getName();
  571.                 $bundleDashboardController 'App\IlaveU\Apps\\' $bundleName '\Controller\DashboardController';
  572.                 $dashboard = new $bundleDashboardController();
  573.                 foreach ($dashboard->configureMenuItems() as $menu) {
  574.                     yield $menu;
  575.                 }
  576.             }
  577.         }
  578.         /* END : Les Extensions IlaveU */
  579.         yield MenuItem::section('Commandes');
  580.         
  581.         yield MenuItem::linkToCrud('Commandes réglées''fas fa-check-circle'Order::class)
  582.             ->setController(PaidOrdersCrudController::class);
  583.         yield MenuItem::linkToCrud('Commandes à payer''fas fa-clock'Order::class)
  584.             ->setController(UnpaidOrdersCrudController::class);
  585.         yield MenuItem::section('Parametres');
  586.         yield MenuItem::linkToRoute('Theme Designer''fas fa-shield-alt'"website_theme_grapesjs_edit")->setPermission("ROLE_ADMIN_DEV");
  587.         yield MenuItem::linkToRoute('Apps Store''fas fa-shield-alt'"app_store")->setPermission("ROLE_ADMIN_DEV");
  588.         yield MenuItem::linkToCrud('Utilisateurs''fas fa-shield-alt'User::class)->setController(UserCrudController::class);
  589.         yield MenuItem::linkToCrud('Passwords''fas fa-shield-alt'User::class)->setController(UserPasswordCrudController::class);
  590.         yield MenuItem::linkToCrud('Roles''fas fa-shield-alt'Role::class);
  591.         yield MenuItem::linkToCrud('LinkType''fas fa-gears'LinkType::class)->setPermission("ROLE_ADMIN_DEV");
  592.         yield MenuItem::linkToCrud('Link''fas fa-gears'Link::class)->setPermission("ROLE_ADMIN_DEV");
  593.         yield MenuItem::linkToCrud('Applications''fas fa-shield-alt'Application::class)->setPermission("ROLE_ADMIN_DEV");
  594.         yield MenuItem::linkToCrud('Notifications''fas fa-shield-alt'Notification::class)->setPermission("ROLE_ADMIN_DEV");
  595.         yield MenuItem::linkToRoute('Text to speech''fas fa-shield-alt'"open_ai_tts")->setPermission("ROLE_ADMIN_DEV");
  596.         yield MenuItem::linkToCrud('Historique''fas fa-shield-alt'EntityLog::class)->setPermission("ROLE_ADMIN");
  597.         yield MenuItem::linkToCrud('Settings''fas fa-shield-alt'Settings::class)
  598.             ->setAction("edit")
  599.             ->setEntityId($settings->getId());
  600.         yield MenuItem::linkToCrud('Currency Exchange''fas fa-shield-alt'CurrencyExchangeRate::class);
  601.         yield MenuItem::linkToCrud('Web site theme''fas fa-shield-alt'WebsiteTheme::class)->setPermission("ROLE_ADMIN_DEV");
  602.         yield MenuItem::linkToCrud('Front Themes''fas fa-shield-alt'FrontTheme::class)->setPermission("ROLE_ADMIN_DEV");
  603.         yield MenuItem::linkToCrud('Export Excel''fas fa-shield-alt'ExportExcel::class)->setController(ExportExcelCrudController::class)->setPermission("ROLE_ADMIN_DEV");
  604.     }
  605. }