<?php
namespace App\Controller\Admin;
use App\Entity\Activity;
use App\Repository\ActivityRepository;
use App\Repository\CommentRepository;
use App\Repository\ProduitDeclinationValueRepository;
use App\Repository\ProduitRepository;
use App\Repository\ReclamationRepository;
use App\Repository\SocialConversationRepository;
use App\Repository\UserRepository;
use App\Repository\DocumentRepository;
use Doctrine\ORM\EntityManagerInterface;
use PDO;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Service\GlobalVariables;
use App\Entity\User;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Types\Types;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted(new Expression("is_granted('ROLE_ADMIN') or is_granted('ROLE_RESELLER')"))]
class DashboardController extends AbstractController {
private $produitRepository;
public function __construct(
ProduitRepository $produitRepository,
ActivityRepository $activityRepository,
CommentRepository $commentRepository,
UserRepository $userRepository,
ProduitDeclinationValueRepository $produitDeclinationValueRepository,
DocumentRepository $documentRepository,
GlobalVariables $globalVariables
) {
$this->produitRepository = $produitRepository;
$this->activityRepository = $activityRepository;
$this->commentRepository = $commentRepository;
$this->userRepository = $userRepository;
$this->produitDeclinationValueRepository = $produitDeclinationValueRepository;
$this->produitRepository = $produitRepository;
$this->documentRepository = $documentRepository;
$this->globalVariables = $globalVariables;
}
private function isResellerUser(): bool
{
$user = $this->getUser();
return $user instanceof User
&& in_array('ROLE_RESELLER', $user->getRoles(), true)
&& !in_array('ROLE_SUPER_ADMIN', $user->getRoles(), true);
}
private function getCurrentResellerUser(): ?User
{
$user = $this->getUser();
return $this->isResellerUser() && $user instanceof User ? $user : null;
}
private function canAccessReclamations(): bool
{
$user = $this->getUser();
if ($user instanceof User && in_array('ROLE_SUPER_ADMIN', $user->getRoles(), true)) {
return true;
}
return $user instanceof User && in_array('RECLAMATION', (array) $user->getArrayRight(), true);
}
/**
* @Route("/index", name="index")
*/
public function index(EntityManagerInterface $em): Response {
$user = $this->getUser();
if ($user instanceof User && in_array('ROLE_RESELLER', $user->getRoles(), true) && !in_array('ROLE_SUPER_ADMIN', $user->getRoles(), true)) {
$kpisToday = $this->documentRepository->getDashboardKpisTodayClientOrders($user);
return $this->render('@admin/dashboard/dashboard.html.twig', [
'todayActivities' => [],
'weekActivities' => [],
'customActivities' => [],
'activeTab' => 'today',
'lastDayActivities' => [],
'logEventsActivities' => [],
'log_start' => null,
'log_end' => null,
'totalVente' => (float) ($kpisToday['totalVenteAvecFrais'] ?? 0),
'totalVenteAvecFrais' => (float) ($kpisToday['totalVenteAvecFrais'] ?? 0),
'totalVenteSansFrais' => (float) ($kpisToday['totalVenteSansFrais'] ?? 0),
'comments' => [],
'latestClients' => [],
'countProduitDec' => $this->produitDeclinationValueRepository->countProduitDeclinations(null, null, null, null, null, [], null, true, null, null, null, null, null),
'countProduit' => $this->produitRepository->countProduits(null, null, null, null, true, null),
'countClient' => 0,
'nbCommandeJour' => (int) ($kpisToday['nbCommandeJour'] ?? 0),
'nbEchangeJour' => (int) ($kpisToday['nbEchangeJour'] ?? 0),
'nbUsersConnectes' => 0,
'log_user' => 'all',
'users' => [],
'isResellerView' => true,
]);
}
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$now = new \DateTime("now");
$todayActivities = [];
$lastDayActivities = [];
$weekActivities = [];
$customActivities = [];
$logEventsActivities = [];
$totalVenteAvecFrais = 0.0; // somme TTC
$totalVenteSansFrais = 0.0; // somme TTC - frais livraison
$totalVente = 0.0;
$nbCommandeJour = 0;
$nbEchangeJour = 0;
if ($user && in_array("ROLE_SUPER_ADMIN", $user->getRoles())) {
$todayActivities = $this->activityRepository->findByCreatedAtField($now, 100);
$kpisToday = $this->documentRepository->getDashboardKpisTodayClientOrders();
$totalVenteAvecFrais = (float) ($kpisToday['totalVenteAvecFrais'] ?? 0);
$totalVenteSansFrais = (float) ($kpisToday['totalVenteSansFrais'] ?? 0);
$totalVente = $totalVenteAvecFrais;
$nbCommandeJour = (int) ($kpisToday['nbCommandeJour'] ?? 0);
$nbEchangeJour = (int) ($kpisToday['nbEchangeJour'] ?? 0);
$lastDayActivities = $this->activityRepository->findByCreatedAtField(new \DateTime("-1 day"), 100);
//$lastDaylogEventsActivities = $this->activityRepository->LoginEvent(new \DateTime("-1 day"), 100);
} else {
}
$comments = $this->commentRepository->findLatestComments(20);
$latestClients = $this->userRepository->findLatestUsers(10, 'client');
$countProduitDec = $this->produitDeclinationValueRepository->countProduitDeclinations(null, null, null, null, null, [], null, true, null, null, null, null, null);
$countProduit = $this->produitRepository->countProduits(null, null, null, null, true, null);
$countClient = $this->userRepository->countUsers(null, null, 'client');
//$nbCommandeJour = $this->documentRepository->countCommandesForToday();
$nbUsersConnectes = $this->activityRepository->countTodayLogins(); // à créer
$startOfWeek = (new \DateTime())->modify('monday this week')->setTime(0, 0, 0);
$endOfWeek = (new \DateTime())->modify('sunday this week')->setTime(23, 59, 59);
$weekActivities = $this->activityRepository->findBetweenDates($startOfWeek, $endOfWeek);
$customActivities = [];
$startStr = $_GET['start'] ?? null;
$endStr = $_GET['end'] ?? null;
if ($startStr && $endStr) {
try {
$startDate = new \DateTime($startStr);
$endDate = new \DateTime($endStr);
$customActivities = $this->activityRepository->findBetweenDates($startDate, $endDate);
} catch (\Exception $e) {
// Gérer une erreur de date invalide si besoin
}
}
$activeTab = 'today'; // onglet par défaut
if ($startStr && $endStr) {
$activeTab = 'custom'; // si période personnalisée
}
$selectedUserId = $_GET['log_user'] ?? 'all';
if ($selectedUserId !== 'all') {
$selectedUserId = (int) $selectedUserId;
}
$logEventsActivities = [];
$startLog = $_GET['log_start'] ?? (new \DateTime())->modify('monday this week')->format('Y-m-d');
$endLog = $_GET['log_end'] ?? (new \DateTime())->modify('sunday this week')->format('Y-m-d');
try {
$startDate = new \DateTime($startLog);
$endDate = new \DateTime($endLog);
if ($selectedUserId === 'all') {
$logEventsActivities = $this->activityRepository->LoginEventRange($startDate, $endDate);
} else {
$logEventsActivities = $this->activityRepository->LoginEventRangeByUser($startDate, $endDate, $selectedUserId);
}
} catch (\Exception $e) {
$logEventsActivities = [];
}
return $this->render('@admin/dashboard/dashboard.html.twig', [
'todayActivities' => $todayActivities,
'weekActivities' => $weekActivities,
'customActivities' => $customActivities,
'activeTab' => $activeTab,
'lastDayActivities' => $lastDayActivities,
'logEventsActivities' => $logEventsActivities ?? null,
'log_start' => $startLog,
'log_end' => $endLog,
//'lastDaylogEventsActivities' => $lastDaylogEventsActivities ?? null,
// *** sorties totaux
'totalVente' => $totalVente, // legacy (avec frais)
'totalVenteAvecFrais' => $totalVenteAvecFrais, // nouveau
'totalVenteSansFrais' => $totalVenteSansFrais, // nouveau
'comments' => $comments,
'latestClients' => $latestClients,
'countProduitDec' => $countProduitDec,
'countProduit' => $countProduit,
'countClient' => $countClient,
'nbCommandeJour' => $nbCommandeJour,
'nbUsersConnectes' => $nbUsersConnectes,
'log_user' => $selectedUserId,
'users' => $this->userRepository->findLightUsers(),
'isResellerView' => false,
]);
}
/**
* @Route("/dashboard/activities", name="dashboard_activities_timeline", methods={"GET"})
* @Route("/admin/dashboard/activities", name="dashboard_activities_timeline_legacy", methods={"GET"})
*/
public function activitiesTimeline(Request $request, EntityManagerInterface $em): Response
{
if ($this->isResellerUser()) {
return new Response($this->renderView('@admin/dashboard/_activities_timeline.html.twig', [
'activities' => [],
]), 200, ['Content-Type' => 'text/html; charset=UTF-8']);
}
// Accès (même logique que ton écran) : SUPER_ADMIN ou droit STATISTIQUE si dispo.
$user = $this->getUser();
$can = $user && in_array('ROLE_SUPER_ADMIN', (array)$user->getRoles(), true);
if (!$can && $user && method_exists($user, 'getArrayRight')) {
$can = in_array('STATISTIQUE', (array)$user->getArrayRight(), true);
}
if (!$can) {
return new Response('Forbidden', 403);
}
$tz = new \DateTimeZone('Africa/Tunis');
$now = new \DateTimeImmutable('now', $tz);
$period = (string) $request->query->get('period', 'today');
// défaut : aujourd’hui
$start = $now->setTime(0, 0, 0);
$end = $now->setTime(23, 59, 59);
switch ($period) {
case '3days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P2D'))->setTime(0,0,0);
break;
case '7days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P6D'))->setTime(0,0,0);
break;
case '30days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P29D'))->setTime(0,0,0);
break;
case 'week':
$start = (new \DateTimeImmutable('monday this week', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_week':
$start = (new \DateTimeImmutable('monday last week', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('sunday last week', $tz))->setTime(23,59,59);
break;
case 'month':
$start = (new \DateTimeImmutable('first day of this month', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_month':
$start = (new \DateTimeImmutable('first day of last month', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('last day of last month', $tz))->setTime(23,59,59);
break;
case 'year':
$start = (new \DateTimeImmutable('first day of january', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_year':
$start = (new \DateTimeImmutable('first day of january last year', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('last day of december last year', $tz))->setTime(23,59,59);
break;
case 'custom': {
$s = $request->query->get('start');
$e = $request->query->get('end');
if ($s && $e) {
try {
$start = (new \DateTimeImmutable($s, $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable($e, $tz))->setTime(23,59,59);
if ($end < $start) { [$start, $end] = [$end, $start]; }
} catch (\Throwable $ex) { /* on garde today */ }
}
break;
}
// today => défaut
}
// Requête explicite, bornes inclusives, SANS limite cachée
$qb = $em->createQueryBuilder()
->select('a','u','p','d')
->from(Activity::class, 'a')
->leftJoin('a.currentUser', 'u')
->leftJoin('a.produit', 'p')
->leftJoin('a.document', 'd')
->andWhere('a.createdAt >= :start')
->andWhere('a.createdAt <= :end')
->setParameter('start', \DateTime::createFromImmutable($start))
->setParameter('end', \DateTime::createFromImmutable($end))
->orderBy('a.createdAt', 'DESC');
$qb->setMaxResults(null); // safety: pas de cap
$activities = $qb->getQuery()->getResult();
$html = $this->renderView('@admin/dashboard/_activities_timeline.html.twig', [
'activities' => $activities,
]);
return new Response($html, 200, ['Content-Type' => 'text/html; charset=UTF-8']);
}
/**
* @Route("/dashboard/login-history", name="dashboard_login_history", methods={"GET"})
* @Route("/admin/dashboard/login-history", name="dashboard_login_history_legacy", methods={"GET"})
*/
public function loginHistory(Request $request): Response
{
if ($this->isResellerUser()) {
return new Response($this->renderView('@admin/dashboard/_login_events_list.html.twig', [
'events' => [],
]));
}
// Accès : SUPER_ADMIN ou droit 'STATISTIQUE' dans arrayRight
$user = $this->getUser();
$can = $user && in_array('ROLE_SUPER_ADMIN', (array)$user->getRoles(), true);
if (!$can && $user && method_exists($user, 'getArrayRight')) {
$can = in_array('STATISTIQUE', (array)$user->getArrayRight(), true);
}
if (!$can) {
return new Response('Forbidden', 403);
}
$tz = new \DateTimeZone('Africa/Tunis');
$now = new \DateTimeImmutable('now', $tz);
$period = (string) $request->query->get('period', 'today');
$logUser = $request->query->get('log_user', 'all');
// Par défaut : aujourd’hui (00:00 → 23:59:59)
$start = $now->setTime(0, 0, 0);
$end = $now->setTime(23, 59, 59);
switch ($period) {
case '3days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P2D'))->setTime(0,0,0);
break;
case '7days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P6D'))->setTime(0,0,0);
break;
case '30days':
$end = $now->setTime(23,59,59);
$start = $end->sub(new \DateInterval('P29D'))->setTime(0,0,0);
break;
case 'week':
$start = (new \DateTimeImmutable('monday this week', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_week':
$start = (new \DateTimeImmutable('monday last week', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('sunday last week', $tz))->setTime(23,59,59);
break;
case 'month':
$start = (new \DateTimeImmutable('first day of this month', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_month':
$start = (new \DateTimeImmutable('first day of last month', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('last day of last month', $tz))->setTime(23,59,59);
break;
case 'year':
$start = (new \DateTimeImmutable('first day of january', $tz))->setTime(0,0,0);
$end = $now->setTime(23,59,59);
break;
case 'last_year':
$start = (new \DateTimeImmutable('first day of january last year', $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable('last day of december last year', $tz))->setTime(23,59,59);
break;
case 'custom':
$s = $request->query->get('start');
$e = $request->query->get('end');
if ($s && $e) {
try {
$start = (new \DateTimeImmutable($s, $tz))->setTime(0,0,0);
$end = (new \DateTimeImmutable($e, $tz))->setTime(23,59,59);
if ($end < $start) { [$start, $end] = [$end, $start]; }
} catch (\Throwable $ex) { /* on conserve today */ }
}
break;
// today => défaut
}
// Utilise tes méthodes existantes du repository
if ($logUser === 'all') {
$events = $this->activityRepository->LoginEventRange(
\DateTime::createFromImmutable($start),
\DateTime::createFromImmutable($end)
);
} else {
$events = $this->activityRepository->LoginEventRangeByUser(
\DateTime::createFromImmutable($start),
\DateTime::createFromImmutable($end),
(int) $logUser
);
}
$html = $this->renderView('@admin/dashboard/_login_events_list.html.twig', [
'events' => $events
]);
return new Response($html);
}
/**
* @Route("/page", name="page")
*/
public function page(): Response {
$produits = $this->produitRepository->findWithImage();
return $this->render('@admin/front/page.html.twig', [
'produits' => $produits
]);
}
public static function ddQuery($sql,EntityManagerInterface $em)
{
//$sql = " select @@sql_mode";
$stmt = $em->getConnection()->prepare($sql);
$result = $stmt->executeQuery()->fetchAllAssociative();
}
/**
* @Route("/count-document/{type}/{status}", name="count-document", methods={"GET","POST"}, options={"expose"=true})
*/
public function countDocument($type="commande",$status="en-attente"): Response {
return new JsonResponse(array(
'result' => 1,
'message' => "{type:'$type',status:'$status'}",
'count' => $this->globalVariables->getCountDocumentByType($type,$status)['count']));
}
/**
* @Route("/time-server", name="timeServer", methods={"GET","POST"}, options={"expose"=true})
*/
public function getTimeServer(): Response {
return new JsonResponse(array(
'result' => 1,
'message' => 'ok',
'data' => (new \DateTime('now'))->format("Y-m-d\\TH:i:s")));
}
/**
* @Route("/copy-db", name="copydatabase")
*/
public function copyDB( LoggerInterface $logger): Response{
$sourceDbName = 'sunshiladmin';
$destinationDbName = 'sunshiladmindemo';
$connectionSource = new PDO('mysql:host=sunshiladmin.mysql.db;dbname=sunshiladmin', 'sunshiladmin', 'SunshineElegance192510185');
$connectionDestination = new PDO('mysql:host=sunshiladmindemo.mysql.db;dbname=sunshiladmindemo' , 'sunshiladmindemo', 'adminDemo2022');
$tables = $connectionSource->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
$connectionDestination->exec("USE {$destinationDbName}");
foreach ($tables as $tableName) {
$createCommand = $connectionSource->query("SHOW CREATE TABLE `{$sourceDbName}`.`{$tableName}`")->fetchColumn(1);
$carefulCreateCommand = str_replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS", $createCommand);
$connectionDestination->exec($carefulCreateCommand);
$logger->info("Table `{$tableName}` created" . PHP_EOL);
$connectionDestination->exec("INSERT INTO `{$destinationDbName}`.`{$tableName}` SELECT * FROM `{$sourceDbName}`.`{$tableName}`");
$logger->info("Data for table `{$tableName}` copied" . PHP_EOL);
}
return new Response("<html><head></head><body>done</body></html>");
}
#[Route('/topnav/status-counts', name: 'admin_topnav_status_counts', methods: ['GET'])]
public function topnavStatusCounts(DocumentRepository $documentRepository, ReclamationRepository $reclamationRepository, SocialConversationRepository $socialConversationRepository, \App\Repository\DraftOrderRepository $draftOrderRepository, \App\Repository\DocumentCallRepository $documentCallRepository): JsonResponse
{
if ($this->isResellerUser()) {
$data = $documentRepository->getTopnavStatusCounts($this->getCurrentResellerUser());
$data['reclamation'] = ['open' => 0];
$data['social_inbox'] = ['unread' => 0];
$data['draft_order'] = ['open' => 0];
$data['call_reminder'] = ['due' => 0];
return new JsonResponse([
'commande' => $data['commande'],
'echange' => $data['echange'],
'reclamation' => $data['reclamation'],
'social_inbox' => $data['social_inbox'],
'draft_order' => $data['draft_order'],
'call_reminder' => $data['call_reminder'],
], 200, [
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
]);
}
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$data = $documentRepository->getTopnavStatusCounts();
$data['reclamation'] = [
'open' => $this->canAccessReclamations() ? $reclamationRepository->countUnresolved() : 0,
];
$data['social_inbox'] = [
'unread' => $socialConversationRepository->countUnreadInbox(),
];
$data['draft_order'] = [
'open' => $draftOrderRepository->countOpen(),
];
$data['call_reminder'] = [
'due' => $documentCallRepository->countDueUnresolvedScheduledCalls(),
];
$response = new JsonResponse($data);
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
$response->headers->set('Pragma', 'no-cache');
return $response;
}
#[Route('/topnav/call-reminders', name: 'admin_topnav_call_reminders', methods: ['GET'])]
public function topnavCallReminders(\App\Repository\DocumentCallRepository $documentCallRepository): JsonResponse
{
if ($this->isResellerUser()) {
return new JsonResponse([
'count' => 0,
'items' => [],
], 200, [
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
]);
}
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$items = [];
foreach ($documentCallRepository->findDueUnresolvedScheduledCalls(12) as $call) {
$document = $call->getDocument();
if (!$document) {
continue;
}
$client = $document->getClient();
$items[] = [
'id' => $call->getId(),
'documentId' => $document->getId(),
'documentRef' => $document->getInternalNbr() ?: ('#' . $document->getId()),
'client' => $client ? trim((string) $client->getFirstName()) : '-',
'phone' => $client ? ($client->getPhone() ?: $client->getSecondPhone()) : null,
'scheduledAt' => $call->getScheduledAt() ? $call->getScheduledAt()->format('d/m/Y H:i') : null,
'note' => $call->getNote(),
'url' => $this->generateUrl('document_show', ['id' => $document->getId()]),
];
}
return new JsonResponse([
'count' => count($items),
'items' => $items,
], 200, [
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
]);
}
#[Route('/dashboard/kpis-today', name: 'admin_dashboard_kpis_today', methods: ['GET'])]
#[Route('/admin/dashboard/kpis-today', name: 'admin_dashboard_kpis_today_legacy', methods: ['GET'])]
public function kpisToday(DocumentRepository $documentRepository): JsonResponse
{
if ($this->isResellerUser()) {
return new JsonResponse(
$documentRepository->getDashboardKpisTodayClientOrders($this->getCurrentResellerUser()),
200,
[
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
]
);
}
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$kpis = $documentRepository->getDashboardKpisTodayClientOrders();
return new JsonResponse($kpis, 200, [
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Pragma' => 'no-cache',
]);
}
}