src/Controller/Admin/DashboardController.php line 97
<?php
namespace App\Controller\Admin;
use App\Entity\Application;
use App\Entity\CurrencyExchangeRate;
use App\Entity\ExportExcel;
use App\Entity\FrontTheme;
use App\Entity\Link;
use App\Entity\LinkType;
use App\Entity\LogHistory;
use App\Entity\Notification;
use App\Entity\Role;
use App\Entity\Settings;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\User;
use App\Entity\WebsiteTheme;
use App\IlaveU\ShopBundle\Entity\Order\Order;
use App\IlaveU\ShopBundle\Entity\Product\Product;
use App\IlaveU\ShopBundle\Entity\Store\Store;
use App\IlaveU\ShopBundle\Entity\Vendor\Vendor;
use App\IlaveU\ShopBundle\Service\IlaveUShopStatisticProvider;
use App\Repository\ApplicationRepository;
use App\Service\IlaveUSettingsProvider;
use Doctrine\Persistence\ManagerRegistry;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Orm\EntityRepositoryInterface;
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class DashboardController extends AbstractDashboardController
{
public function __construct(
private readonly ManagerRegistry $doctrine,
private readonly IlaveUShopStatisticProvider $ilaveShopStatisticProvider,
private readonly RequestStack $requestStack,
) {}
#[Route(path: '/admin', name: 'admin_non_locale')]
public function indexNonLocale(Request $request): Response
{
return $this->redirectToRoute("admin", ["_locale" => $request->getLocale()]);
}
#[Route('/{_locale}/admin/website-theme/grapesjs_edit', name: 'website_theme_grapesjs_edit')]
public function website_theme_grapesjs_edit(): Response
{
return $this->render('@IlaveU/FrontBundle/Themes/' . $this->container->get('twig')->getGlobals()["settings"]->get()->getAssetFolderName() . '/templates/admin/website-theme/grapesjs.html.twig');
}
#[Route(path: '/{_locale}/admin/apps-store', name: 'app_store')]
public function appStore(Request $request, ApplicationRepository $applicationRepository, IlaveUSettingsProvider $ilaveSettingsProvider): Response
{
$appsArray = [];
foreach ($applicationRepository->findAll() as $singleApp) {
$appsArray[] = [
"id" => $singleApp->getId(),
"name" => $singleApp->getName(),
"image" => $singleApp->getImage(),
"price" => $singleApp->getPrice(),
"pageUrl" => $singleApp->getPageUrl(),
];
}
return $this->render("admin/app-store.html.twig", [
"installedApps" => $appsArray,
"JWT" => $ilaveSettingsProvider->createJWTForUser($this->getUser())
]);
}
#[Route(path: '/{_locale}/admin', name: 'admin')]
public function index(): Response
{
$url = $this->requestStack->getCurrentRequest()->server->get('REQUEST_URI');
$parsedUrl = parse_url($url);
parse_str($parsedUrl['query'] ?? '', $queryParams);
if (count($queryParams) == 0) {
$queryParams['store'] = null;
$queryParams['range'] = null;
$queryParams['from'] = null;
$queryParams['to'] = null;
}
$range = $queryParams['range'] ?? 'today';
$from = $queryParams['from'] ?? null;
$to = $queryParams['to'] ?? null;
$store = $queryParams['store'] ?? null;
if(is_numeric($store)){
$store = $this->doctrine->getRepository(Store::class)->findOneBy(["id" => $store]);
}
//dd($request);
// Get list of stores
$stores = $this->doctrine->getRepository(Store::class)->findAll();
// Handle predefined ranges
$now = new \DateTimeImmutable();
if ($from && $to && $queryParams['range'] == '') {
$start = \DateTimeImmutable::createFromFormat('Y-m-d', $from)?->setTime(0, 0);
$end = \DateTimeImmutable::createFromFormat('Y-m-d', $to)?->setTime(23, 59, 59);
} elseif ($range === 'today') {
$start = $now->modify('today')->setTime(0, 0);
$end = $now->modify('today')->setTime(23, 59, 59);
} elseif ($range === 'yesterday') {
$start = $now->modify('yesterday')->setTime(0, 0);
$end = $now->modify('yesterday')->setTime(23, 59, 59);
} elseif ($range === 'week') {
$start = $now->modify('this week')->setTime(0, 0);
$end = $now->modify('next week')->setTime(0, 0)->modify('-1 second');
} elseif ($range === 'year') {
$start = $now->modify('first day of January')->setTime(0, 0);
$end = $now->modify('last day of December')->setTime(23, 59, 59);
} else { // Month By Default
$start = $now->modify('first day of this month')->setTime(0, 0);
$end = $now->modify('last day of this month')->setTime(23, 59, 59);
}
// If custom date range provided (overrides predefined range)
$validOrders = $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
->select('count(orderAlias.id)')
->andWhere("orderAlias.status <> 'draft'")
->andWhere("orderAlias.status <> 'cancelled'")
->andWhere("orderAlias.statusShipping <> 'annulee'")
->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$validOrders->andWhere("orderAlias.store = :store");
$validOrders->setParameter('store', $store);
}
$validOrders = $validOrders->getQuery()->getSingleScalarResult();
$cancelledOrders = $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
->select('count(orderAlias.id)')
->andWhere("orderAlias.status = 'cancelled' OR orderAlias.statusShipping = 'annulee'")
->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$cancelledOrders->andWhere("orderAlias.store = :store");
$cancelledOrders->setParameter('store', $store);
}
$cancelledOrders = $cancelledOrders->getQuery()->getSingleScalarResult();
$shippedOrders = $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
->select('count(orderAlias.id)')
->andWhere("orderAlias.statusShipping = 'shipped'")
->andWhere("orderAlias.status <> 'draft'")
->andWhere("orderAlias.status <> 'cancelled'")
->andWhere("orderAlias.statusShipping <> 'annulee'")
->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$shippedOrders->andWhere("orderAlias.store = :store");
$shippedOrders->setParameter('store', $store);
}
$shippedOrders = $shippedOrders->getQuery()->getSingleScalarResult();
$soldProducts = $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
->select('sum(orderItems.quantity)')
->leftJoin("orderAlias.orderItems", "orderItems")
->andWhere("orderAlias.status <> 'draft'")
->andWhere("orderAlias.status <> 'cancelled'")
->andWhere("orderAlias.statusShipping <> 'annulee'")
->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$soldProducts->andWhere("orderAlias.store = :store");
$soldProducts->setParameter('store', $store);
}
$soldProducts = $soldProducts->getQuery()->getSingleScalarResult();
$revenuedProducts = $this->doctrine->getRepository(Order::class)->createQueryBuilder('orderAlias')
->select('sum(orderItems.quantity * orderItems.price)')
->leftJoin("orderAlias.orderItems", "orderItems")
->andWhere("orderAlias.status <> 'draft'")
->andWhere("orderAlias.status <> 'cancelled'")
->andWhere("orderAlias.statusShipping <> 'annulee'")
->andWhere('orderAlias.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$revenuedProducts->andWhere("orderAlias.store = :store");
$revenuedProducts->setParameter('store', $store);
}
$revenuedProducts = $revenuedProducts->getQuery()->getSingleScalarResult();
$averageOrderAmount = $validOrders > 0 ? $revenuedProducts / $validOrders : 0;
//$inTypes = Order::IN_TYPES;
//$outTypes = Order::OUT_TYPES;
// $productInStock = $this->doctrine->createQueryBuilder()
// ->select('p.id AS product_id, p.name AS product_name,p.initialStock AS initial_stock')
// ->addSelect('
// SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) AS stock_in,
// SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END) AS stock_out,
// (
// p.initialStock +
// SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) -
// SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END)
// ) AS current_stock
// ')
// ->from(Product::class, 'p')
// ->leftJoin('p.orderItems', 'oi')
// ->leftJoin('oi.parentOrder', 'o')
// ->groupBy('p.id')
// ->having('
// (
// p.initialStock +
// SUM(CASE WHEN o.type IN (:inTypes) THEN oi.quantity ELSE 0 END) -
// SUM(CASE WHEN o.type IN (:outTypes) THEN oi.quantity ELSE 0 END)
// ) > 0
// ')
// ->andWhere("o.status = '".Order::STATUS_VALIDATED."'")
// //->andWhere('o.createdAt BETWEEN :start AND :end')
// // ->setParameter('start', $start)
// // ->setParameter('end', $end)
// ->setMaxResults(16)
// ->orderBy('current_stock', 'DESC')
// ->getQuery()
// ->getArrayResult();
$mostOrderedCategory = $this->doctrine->getRepository(Order::class)
->createQueryBuilder('o')
->select('c.id, c.name as categoryName, COUNT(DISTINCT o.id) as orderCount')
->join('o.orderItems', 'oi')
->join('oi.product', 'p')
->join('p.categoriesProduct', 'c') // Assuming products have a many-to-many relationship with categories
->where("o.status NOT IN ('draft', 'cancelled') AND o.statusShipping <> 'annulee'")
->andWhere('o.createdAt BETWEEN :start AND :end')
->groupBy('c.id, c.name')
->orderBy('orderCount', 'DESC')
->setMaxResults(1) // Get only the top category
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$mostOrderedCategory->andWhere("o.store = :store");
$mostOrderedCategory->setParameter('store', $store);
}
$mostOrderedCategory->getQuery()->getOneOrNullResult();
$mostOrderedCategory = $mostOrderedCategory->getQuery()->getOneOrNullResult();
$mostOrderedCategoryName = $mostOrderedCategory ? $mostOrderedCategory['categoryName'] : 'N/A';
// Get the most ordered product
$mostOrderedProduct = $this->doctrine->getRepository(Order::class)
->createQueryBuilder('o')
->select('p.id, p.name as productName, SUM(oi.quantity) as totalQuantity, COUNT(DISTINCT o.id) as orderCount')
->join('o.orderItems', 'oi')
->join('oi.product', 'p')
->where("o.status NOT IN ('draft', 'cancelled') AND o.statusShipping <> 'annulee'")
->andWhere('o.createdAt BETWEEN :start AND :end')
->groupBy('p.id, p.name')
->orderBy('totalQuantity', 'DESC')
->setMaxResults(1)
->setParameter('start', $start)
->setParameter('end', $end);
if($store){
$mostOrderedProduct->andWhere("o.store = :store");
$mostOrderedProduct->setParameter('store', $store);
}
$mostOrderedProduct = $mostOrderedProduct->getQuery()->getOneOrNullResult();
$mostOrderedProductName = $mostOrderedProduct ? $mostOrderedProduct['productName'] : 'N/A';
$stats = [
[
'label' => 'Chiffre d\'affaires',
'value' => (float)round($revenuedProducts, 2),
'icon' => 'fa-coins',
'cssClass' => 'stat-revenue',
'description' => 'Revenu total généré par les ventes',
'url' => null,
'unit' => 'MAD',
],
[
'label' => 'Ventes',
'value' => (int)$validOrders,
'icon' => 'fa-calendar-check',
'cssClass' => 'stat-monthly-orders',
'description' => 'Nombre de ventes validées',
'url' => null,
'unit' => null,
],
[
'label' => 'Produits vendus',
'value' => (int)$soldProducts,
'icon' => 'fa-boxes',
'cssClass' => 'stat-products-sold',
'description' => 'Nombre total de produits vendus',
'url' => null,
'unit' => null,
],
[
'label' => 'Panier moyen',
'value' => (float)round($averageOrderAmount, 2),
'icon' => 'fa-shopping-basket',
'cssClass' => 'stat-average-basket',
'description' => 'Montant moyen dépensé par commande',
'url' => null,
'unit' => 'MAD',
],
[
'label' => 'Commandes annulées',
'value' => (float)$cancelledOrders,
'icon' => 'fa-undo',
'cssClass' => 'stat-refunds',
'description' => 'Nombre de commandes annulées',
'url' => null,
'unit' => null,
],
[
'label' => 'Commandes livrées',
'value' => (float)$shippedOrders,
'icon' => 'fa-truck',
'cssClass' => 'stat-delivered-orders',
'description' => 'Nombre de commandes livrées',
'url' => null,
'unit' => null,
],
[
'label' => 'Catégorie phare',
'value' => $mostOrderedCategoryName,
'icon' => 'fa-star',
'cssClass' => 'stat-top-category',
'description' => 'Catégorie la plus vendue ce mois-ci',
'url' => null,
'unit' => null,
],
[
'label' => 'Produit phare',
'value' => $mostOrderedProductName,
'icon' => 'fa-star',
'cssClass' => 'stat-top-product',
'description' => 'Produit la plus vendu ce mois-ci',
'url' => null,
'unit' => null,
],
];
$productLabels = [];
$productChartData = [];
// foreach ($productInStock as $product) {
// $productLabels[] = $product['product_name'];
// $productChartData[] = (float) $product['current_stock']; // cast to float for Chart.js
// }
// $productChartData = [
// 'labels' => $productLabels,
// 'data' => $productChartData,
// ];
return $this->render(
"bundles/EasyAdminBundle/welcome.html.twig",
[
'stores' => $stores,
'stats' => $stats,
'productChartData' => $productChartData,
'queryParams' => $queryParams,
]
);
}
public function configureCrud(): Crud
{
return
Crud::new()
->setDefaultSort(["id" => 'DESC'])
->setPaginatorPageSize(12)
;
}
public function configureDashboard(): Dashboard
{
$mainSettings = $this->doctrine->getManager()->getRepository(Settings::class)->findOneBy(["code" => "main"]);
$nameSpaceTrans = strtolower($mainSettings->getProjectName()) . "-admin";
$urlImage = "../themes/" . strtolower($mainSettings->getAssetFolderName()) . "/admin/images/logo.png";
return Dashboard::new()
->setTitle('<img title="Dashboard" src="' . $urlImage . '" />
')
//->renderContentMaximized()
->setFaviconPath("../themes/" . strtolower($mainSettings->getAssetFolderName()) . "/admin/images/icon.png")
->setTranslationDomain($nameSpaceTrans)
->disableUrlSignatures()
->setLocales(
[
'fr' => 'Français',
'en' => 'English',
'ar' => 'Arabe',
]
)
;
}
public function configureMenuItems(): iterable
{
/* START : Les Extensions IlaveU */
$applications = $this->doctrine->getManager()->getRepository(Application::class)->findBy(["isEnabled" => true], ["menuOrder" => "ASC"]);
$settings = $this->doctrine->getManager()->getRepository(Settings::class)->findOneBy(["code" => "main"]);
//$finder = new Finder();
$filesystem = new Filesystem();
//$finder->directories()->in(__DIR__."/../../IlaveU")->depth('== 0');
// For Principal Bundles (ShopBundle + FrontBundle + ...)
foreach ($applications as $singleApplication) {
$bundleExist = $filesystem->exists(__DIR__ . "/../../IlaveU/" . $singleApplication->getName() . "/IlaveU" . $singleApplication->getName() . ".php");
if (!$bundleExist) {
continue;
}
$bundleName = $singleApplication->getName();
// Les themes systemes IlaveU (FrontBundle Themes)
if ($bundleName == "FrontBundle") {
$bundleDashboardController = 'App\IlaveU\FrontBundle\Themes\\' . $settings->getFrontTheme() . '\Controller\DashboardController';
} else {
$bundleDashboardController = 'App\IlaveU\\' . $bundleName . '\Controller\DashboardController';
}
$dashboard = new $bundleDashboardController();
foreach ($dashboard->configureMenuItems() as $menu) {
yield $menu;
}
}
// For Additional Apps Bundles (POSBundle + OtherBundle + ...)
foreach ($applications as $singleApplication) {
if ($singleApplication->getParentApplication()) {
continue;
}
$menuArray = [];
$bundleExist = $filesystem->exists(__DIR__ . "/../../IlaveU/Apps/" . $singleApplication->getName() . "/IlaveU" . $singleApplication->getName() . ".php");
if (!$bundleExist) {
continue;
}
$bundleName = $singleApplication->getName();
$bundleDashboardController = 'App\IlaveU\Apps\\' . $bundleName . '\Controller\DashboardController';
$dashboard = new $bundleDashboardController();
foreach ($dashboard->configureMenuItems() as $menu) {
yield $menu;
}
//SubApplications
foreach ($singleApplication->getSubApplications() as $subApplication) {
$bundleExist = $filesystem->exists(__DIR__ . "/../../IlaveU/Apps/" . $subApplication->getName() . "/IlaveU" . $subApplication->getName() . ".php");
if (!$bundleExist) {
continue;
}
$bundleName = $subApplication->getName();
$bundleDashboardController = 'App\IlaveU\Apps\\' . $bundleName . '\Controller\DashboardController';
$dashboard = new $bundleDashboardController();
foreach ($dashboard->configureMenuItems() as $menu) {
yield $menu;
}
}
}
/* END : Les Extensions IlaveU */
yield MenuItem::section('Parametres');
yield MenuItem::linkToRoute('Theme Designer', 'fas fa-shield-alt', "website_theme_grapesjs_edit")->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToRoute('Apps Store', 'fas fa-shield-alt', "app_store")->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Utilisateurs', 'fas fa-shield-alt', User::class)->setController(UserCrudController::class);
yield MenuItem::linkToCrud('Passwords', 'fas fa-shield-alt', User::class)->setController(UserPasswordCrudController::class);
yield MenuItem::linkToCrud('Roles', 'fas fa-shield-alt', Role::class);
yield MenuItem::linkToCrud('LinkType', 'fas fa-gears', LinkType::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Link', 'fas fa-gears', Link::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Applications', 'fas fa-shield-alt', Application::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Notifications', 'fas fa-shield-alt', Notification::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToRoute('Text to speech', 'fas fa-shield-alt', "open_ai_tts")->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('LogHistory', 'fas fa-shield-alt', LogHistory::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Settings', 'fas fa-shield-alt', Settings::class)
->setAction("edit")
->setEntityId($settings->getId());
yield MenuItem::linkToCrud('Currency Exchange', 'fas fa-shield-alt', CurrencyExchangeRate::class);
yield MenuItem::linkToCrud('Web site theme', 'fas fa-shield-alt', WebsiteTheme::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Front Themes', 'fas fa-shield-alt', FrontTheme::class)->setPermission("ROLE_ADMIN_DEV");
yield MenuItem::linkToCrud('Export Excel', 'fas fa-shield-alt', ExportExcel::class)->setController(ExportExcelCrudController::class)->setPermission("ROLE_ADMIN_DEV");
}
}