mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 03:08:25 +00:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			v3.0.0-RC5
			...
			68-feature
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2fc6e18d0f | |||
| 
						 | 
					80ea4bbdd2 | ||
| 
						 | 
					801c693ef9 | ||
| 
						 | 
					c1c9562e67 | ||
| 
						 | 
					b9e580af9a | ||
| 
						 | 
					790c7f6724 | ||
| 
						 | 
					1be91bb392 | ||
| 
						 | 
					93f39ebe5b | ||
| 
						 | 
					176c3c0e27 | ||
| 
						 | 
					59cd8466be | ||
| 
						 | 
					ef9e872394 | ||
| 
						 | 
					51a46ab5d7 | ||
| 
						 | 
					191b416c6c | ||
| 6028efdc7c | |||
| 
						 | 
					60c9e037a6 | 
@@ -31,7 +31,9 @@
 | 
			
		||||
    "typescript": "^4.7.2",
 | 
			
		||||
    "vue-loader": "^17.0.0",
 | 
			
		||||
    "webpack": "^5.75.0",
 | 
			
		||||
    "webpack-cli": "^5.0.1"
 | 
			
		||||
    "webpack-cli": "^5.0.1",
 | 
			
		||||
      "chart.js": "^4.2.1",
 | 
			
		||||
      "vue-chartjs": "^5.2.0"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@fullcalendar/core": "^6.1.4",
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
 | 
			
		||||
use Chill\MainBundle\Service\EntityInfo\ViewEntityInfoProviderInterface;
 | 
			
		||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
 | 
			
		||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
 | 
			
		||||
use Chill\MainBundle\Widget\WidgetHandlerInterface;
 | 
			
		||||
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
 | 
			
		||||
@@ -62,9 +63,10 @@ class ChillMainBundle extends Bundle
 | 
			
		||||
            ->addTag('chill_main.workflow_handler');
 | 
			
		||||
        $container->registerForAutoconfiguration(CronJobInterface::class)
 | 
			
		||||
            ->addTag('chill_main.cron_job');
 | 
			
		||||
        $container->registerForAutoconfiguration(WidgetHandlerInterface::class)
 | 
			
		||||
            ->addTag('chill_main.widget_handler');
 | 
			
		||||
        $container->registerForAutoconfiguration(ViewEntityInfoProviderInterface::class)
 | 
			
		||||
            ->addTag('chill_main.entity_info_provider');
 | 
			
		||||
 | 
			
		||||
        $container->addCompilerPass(new SearchableServicesCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new ConfigConsistencyCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,133 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactory;
 | 
			
		||||
use Chill\MainBundle\Repository\UserRepository;
 | 
			
		||||
use Chill\MainBundle\Serializer\Model\Collection;
 | 
			
		||||
use Chill\MainBundle\Widget\WidgetHandlerManager;
 | 
			
		||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use PHPUnit\Util\Json;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\HttpFoundation\JsonResponse;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 | 
			
		||||
use Symfony\Component\Serializer\Serializer;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Route("/{_locale}/dashboard")
 | 
			
		||||
 */
 | 
			
		||||
class DashboardHomepageController extends AbstractController{
 | 
			
		||||
 | 
			
		||||
    private WidgetHandlerManager $widgetHandlerManager;
 | 
			
		||||
 | 
			
		||||
    private Security $security;
 | 
			
		||||
 | 
			
		||||
    private UserRepository $userRepository;
 | 
			
		||||
 | 
			
		||||
    private EntityManagerInterface $em;
 | 
			
		||||
 | 
			
		||||
    private LoggerInterface $logger;
 | 
			
		||||
 | 
			
		||||
    private SerializerInterface $serializer;
 | 
			
		||||
 | 
			
		||||
    private PaginatorFactory $paginatorFactory;
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        //AccompanyingPeriodRepository $accompanyingPeriodRepository,
 | 
			
		||||
        WidgetHandlerManager $widgetHandlerManager,
 | 
			
		||||
        Security $security,
 | 
			
		||||
        UserRepository $userRepository,
 | 
			
		||||
        EntityManagerInterface $em,
 | 
			
		||||
        LoggerInterface $logger,
 | 
			
		||||
        SerializerInterface $serializer,
 | 
			
		||||
        PaginatorFactory $paginatorFactory,
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        //$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
 | 
			
		||||
        $this->widgetHandlerManager = $widgetHandlerManager;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
        $this->userRepository = $userRepository;
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->logger = $logger;
 | 
			
		||||
        $this->serializer = $serializer;
 | 
			
		||||
        $this->paginatorFactory = $paginatorFactory;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @Route("/raw_data", name="chill_main_widget_raw_data")
 | 
			
		||||
     * @return JsonResponse
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    public function index(): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
 | 
			
		||||
 | 
			
		||||
        if (!$this->security->getUser() instanceof User) {
 | 
			
		||||
            throw new AccessDeniedHttpException('You must be authenticated and a user to see the dashboard');
 | 
			
		||||
        }
 | 
			
		||||
        // --------------Bar---------------------
 | 
			
		||||
        $config = [
 | 
			
		||||
            'alias' => 'bar',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $context = [
 | 
			
		||||
            //'user' => $this->security->getUser(),
 | 
			
		||||
            //Hardcoder pour resultat
 | 
			
		||||
            'user' => 19,
 | 
			
		||||
            'what' => 'accompanying_period_by_month',
 | 
			
		||||
        ];
 | 
			
		||||
        // --------------Number------------------
 | 
			
		||||
        /*$config = [
 | 
			
		||||
            'alias' => 'number',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $context = [
 | 
			
		||||
            'user' => $this->security->getUser(),
 | 
			
		||||
            //'what' => 'notification_unread',
 | 
			
		||||
            'what' => 'notification_sender',
 | 
			
		||||
        ];*/
 | 
			
		||||
        $data = $this->widgetHandlerManager->getDataForWidget($config, $context);
 | 
			
		||||
 | 
			
		||||
        return new JsonResponse($data,Response::HTTP_OK,[]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @Route("/widget/{context}", name="chill_main_widget_get_data")
 | 
			
		||||
     */
 | 
			
		||||
    public function getDataForWidget(Request $request, string $context): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        //Retrieve data from request in vue component
 | 
			
		||||
        $requestData = json_decode($request->getContent(), true);
 | 
			
		||||
        dump($requestData);
 | 
			
		||||
        // Process the widget data using the WidgetHandlerManager
 | 
			
		||||
        //$handler = $this->widgetHandlerManager->getHandler($requestData);
 | 
			
		||||
        //dump($handler);
 | 
			
		||||
 | 
			
		||||
        // Return the widget data in JSON response
 | 
			
		||||
        //return new JsonResponse($this->getData($requestData));
 | 
			
		||||
 | 
			
		||||
        return new JsonResponse($requestData);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
 | 
			
		||||
class SavedExportApiController extends ApiController
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +20,7 @@ use Chill\MainBundle\Controller\LanguageController;
 | 
			
		||||
use Chill\MainBundle\Controller\LocationController;
 | 
			
		||||
use Chill\MainBundle\Controller\LocationTypeController;
 | 
			
		||||
use Chill\MainBundle\Controller\RegroupmentController;
 | 
			
		||||
use Chill\MainBundle\Controller\SavedExportApiController;
 | 
			
		||||
use Chill\MainBundle\Controller\UserController;
 | 
			
		||||
use Chill\MainBundle\Controller\UserJobApiController;
 | 
			
		||||
use Chill\MainBundle\Controller\UserJobController;
 | 
			
		||||
@@ -54,6 +55,7 @@ use Chill\MainBundle\Entity\Language;
 | 
			
		||||
use Chill\MainBundle\Entity\Location;
 | 
			
		||||
use Chill\MainBundle\Entity\LocationType;
 | 
			
		||||
use Chill\MainBundle\Entity\Regroupment;
 | 
			
		||||
use Chill\MainBundle\Entity\SavedExport;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Entity\UserJob;
 | 
			
		||||
use Chill\MainBundle\Form\CenterType;
 | 
			
		||||
@@ -228,7 +230,7 @@ class ChillMainExtension extends Extension implements
 | 
			
		||||
        $twigConfig = [
 | 
			
		||||
            'globals' => [
 | 
			
		||||
                'installation' => [
 | 
			
		||||
                    'name' => $config['installation_name'], ],
 | 
			
		||||
                    'name' => $config['installation_name'],],
 | 
			
		||||
                'available_languages' => $config['available_languages'],
 | 
			
		||||
                'add_address' => $config['add_address'],
 | 
			
		||||
            ],
 | 
			
		||||
@@ -327,9 +329,9 @@ class ChillMainExtension extends Extension implements
 | 
			
		||||
     * Load parameter for configuration and set parameters for api.
 | 
			
		||||
     */
 | 
			
		||||
    protected function configureCruds(
 | 
			
		||||
        ContainerBuilder $container,
 | 
			
		||||
        array $crudConfig,
 | 
			
		||||
        array $apiConfig,
 | 
			
		||||
        ContainerBuilder      $container,
 | 
			
		||||
        array                 $crudConfig,
 | 
			
		||||
        array                 $apiConfig,
 | 
			
		||||
        Loader\YamlFileLoader $loader
 | 
			
		||||
    ): void {
 | 
			
		||||
        if (0 === \count($crudConfig)) {
 | 
			
		||||
@@ -767,7 +769,28 @@ class ChillMainExtension extends Extension implements
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
                [
 | 
			
		||||
                    'class' => SavedExport::class,
 | 
			
		||||
                    'controller' => SavedExportApiController::class,
 | 
			
		||||
                    'name' => 'saved_export',
 | 
			
		||||
                    'base_path' => '/api/1.0/main/saved-export',
 | 
			
		||||
                    'base_role' => 'ROLE_USER',
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        '_index' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true,
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                        '_entity' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true,
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Ramsey\Uuid\Uuid;
 | 
			
		||||
use Ramsey\Uuid\UuidInterface;
 | 
			
		||||
use Symfony\Component\Validator\Constraints as Assert;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @ORM\Entity
 | 
			
		||||
@@ -35,6 +36,7 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
     * @ORM\Column(type="text", nullable=false, options={"default": ""})
 | 
			
		||||
     *
 | 
			
		||||
     * @Assert\NotBlank
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private string $description = '';
 | 
			
		||||
 | 
			
		||||
@@ -49,11 +51,13 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
     * @ORM\Column(name="id", type="uuid", unique="true")
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\GeneratedValue(strategy="NONE")
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private UuidInterface $id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="json", nullable=false, options={"default": "[]"})
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private array $options = [];
 | 
			
		||||
 | 
			
		||||
@@ -61,11 +65,13 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
     * @ORM\Column(type="text", nullable=false, options={"default": ""})
 | 
			
		||||
     *
 | 
			
		||||
     * @Assert\NotBlank
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private string $title = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity=User::class)
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private User $user;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <span v-if="noResults" class="chill-no-data-statement">{{ $t('no_dashboard') }}</span>
 | 
			
		||||
   <div v-else id="dashboards" class="row g-3" data-masonry='{"percentPosition": true }'>
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      <div class="mbloc col col-sm-6 col-lg-4">
 | 
			
		||||
         <div class="custom1">
 | 
			
		||||
            <ul class="list-unstyled">
 | 
			
		||||
@@ -38,39 +38,33 @@
 | 
			
		||||
            </ul>
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
      <!--
 | 
			
		||||
      <div class="mbloc col col-sm-6 col-lg-4">
 | 
			
		||||
         <div class="custom2">
 | 
			
		||||
            Mon dashboard personnalisé
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="mbloc col col-sm-6 col-lg-4">
 | 
			
		||||
         <div class="custom3">
 | 
			
		||||
            Mon dashboard personnalisé
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="mbloc col col-sm-6 col-lg-4">
 | 
			
		||||
         <div class="custom4">
 | 
			
		||||
            Mon dashboard personnalisé
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      -->
 | 
			
		||||
       <div class="mbloc col col-sm-6 col-lg-4">
 | 
			
		||||
           <div class="custom2">
 | 
			
		||||
               <MyWidget/>
 | 
			
		||||
           </div>
 | 
			
		||||
       </div>
 | 
			
		||||
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
import MyWidget from './MyWidget.vue'
 | 
			
		||||
import { mapGetters } from "vuex";
 | 
			
		||||
import Masonry from 'masonry-layout/masonry';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: "MyCustoms",
 | 
			
		||||
    components:{
 | 
			
		||||
       MyWidget
 | 
			
		||||
    },
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         counterClass: {
 | 
			
		||||
            counter: true   //hack to pass class 'counter' in i18n-t
 | 
			
		||||
         }
 | 
			
		||||
         },
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
@@ -82,8 +76,8 @@ export default {
 | 
			
		||||
   mounted() {
 | 
			
		||||
      const elem = document.querySelector('#dashboards');
 | 
			
		||||
      const masonry = new Masonry(elem, {});
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
   },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@@ -98,4 +92,4 @@ span.counter {
 | 
			
		||||
      background-color: unset;
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,91 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <Bar v-if="loaded_bar" :data="chartData" :options="chartOptions"/>
 | 
			
		||||
        <span v-if="loaded_number">{{ number }}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
    Chart as ChartJS,
 | 
			
		||||
    Title,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    Legend,
 | 
			
		||||
    BarElement,
 | 
			
		||||
    CategoryScale,
 | 
			
		||||
    LinearScale
 | 
			
		||||
} from 'chart.js'
 | 
			
		||||
import {Bar} from 'vue-chartjs'
 | 
			
		||||
import {defineComponent} from 'vue'
 | 
			
		||||
import {makeFetch} from "../../lib/api/apiMethods";
 | 
			
		||||
 | 
			
		||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
    name: 'MyWidget',
 | 
			
		||||
    components: {
 | 
			
		||||
        Bar,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            loaded_bar: false,
 | 
			
		||||
            loaded_number: false,
 | 
			
		||||
            chartData: null,
 | 
			
		||||
            chartOptions: null,
 | 
			
		||||
            number: "",
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    async mounted() {
 | 
			
		||||
        this.loaded_bar = false;
 | 
			
		||||
        this.loaded_number = false;
 | 
			
		||||
        const url = '/fr/dashboard/raw_data';
 | 
			
		||||
        try {
 | 
			
		||||
            const response: { data: any, options: any, type: any } = await makeFetch("GET", url);
 | 
			
		||||
            this.chartData = response.data;
 | 
			
		||||
            this.chartOptions = response.options;
 | 
			
		||||
            if (response.type == 'bar') {
 | 
			
		||||
                this.loaded_bar = true;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.loaded_number = true
 | 
			
		||||
                this.number = response.data.datasets[0].data[0] + " "+ response.data.labels[0];
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.log(error);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    /*methods: {
 | 
			
		||||
        async makeNumberWidget() {
 | 
			
		||||
            let body = {
 | 
			
		||||
                config: {
 | 
			
		||||
                    alias: 'number'
 | 
			
		||||
                },
 | 
			
		||||
                context: {
 | 
			
		||||
                    what: 'notification_unread',
 | 
			
		||||
                    user: 19 //Ne rien mettre ou via les refs
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            try {
 | 
			
		||||
                const response: {
 | 
			
		||||
                    data: any,
 | 
			
		||||
                    options: any,
 | 
			
		||||
                    type: any
 | 
			
		||||
                } = await makeFetch('POST', '/{_locale}//{_locale}/dashboard/widget/number', body)
 | 
			
		||||
                this.chartData = response.data;
 | 
			
		||||
                this.chartOptions = response.options;
 | 
			
		||||
                this.loaded_number = true;
 | 
			
		||||
                this.number = response.data.datasets[0].data[0] + response.data.labels[0];
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.log(error)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
    }*/
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
    span {
 | 
			
		||||
        font-size: x-large;
 | 
			
		||||
        color: #0a53be;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
export const data = {
 | 
			
		||||
    labels: [
 | 
			
		||||
        'January',
 | 
			
		||||
        'February',
 | 
			
		||||
        'March',
 | 
			
		||||
        'April',
 | 
			
		||||
        'May',
 | 
			
		||||
        'June',
 | 
			
		||||
        'July',
 | 
			
		||||
        'August',
 | 
			
		||||
        'September',
 | 
			
		||||
        'October',
 | 
			
		||||
        'November',
 | 
			
		||||
        'December'
 | 
			
		||||
    ],
 | 
			
		||||
    datasets: [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'Data One',
 | 
			
		||||
            backgroundColor: '#f87979',
 | 
			
		||||
            data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11]
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const options = {
 | 
			
		||||
    responsive: true,
 | 
			
		||||
    maintainAspectRatio: false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Bundle/ChillMainBundle/Widget/WidgetHandlerInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Bundle/ChillMainBundle/Widget/WidgetHandlerInterface.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Widget;
 | 
			
		||||
 | 
			
		||||
interface WidgetHandlerInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Return true if the handler supports the handling for this widget.
 | 
			
		||||
     */
 | 
			
		||||
    public function supports(array $config,array $context = []): bool;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return an array which will be passed as data for the chart in the Vue element.
 | 
			
		||||
     */
 | 
			
		||||
    public function getDataForWidget(array $config,array $context = []): array;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/Bundle/ChillMainBundle/Widget/WidgetHandlerManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/Bundle/ChillMainBundle/Widget/WidgetHandlerManager.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Widget;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Widget\WidgetHandlerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
 | 
			
		||||
 | 
			
		||||
final class WidgetHandlerManager
 | 
			
		||||
{
 | 
			
		||||
    private iterable $handlers;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        iterable $handlers
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->handlers = $handlers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getHandler(array $config,): WidgetHandlerInterface
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->handlers as $widget) {
 | 
			
		||||
            if ($widget->supports($config)) {
 | 
			
		||||
                return $widget;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle unsupported context
 | 
			
		||||
        throw new \InvalidArgumentException('Unsupported widget.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDataForWidget(array $config, array $context=[]): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getHandler($config)->getDataForWidget($config,$context);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Widget\Widgets\DataFetcher;
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Doctrine\ORM\Query\ResultSetMapping;
 | 
			
		||||
 | 
			
		||||
class WidgetBarDataFetcher
 | 
			
		||||
{
 | 
			
		||||
    private EntityManagerInterface $entityManager;
 | 
			
		||||
 | 
			
		||||
    public function __construct(EntityManagerInterface $entityManager)
 | 
			
		||||
    {
 | 
			
		||||
        $this->entityManager = $entityManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws \Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchDataForWidget(int $userId): array
 | 
			
		||||
    {
 | 
			
		||||
        $sql = "
 | 
			
		||||
            SELECT DATE_TRUNC('month', ap.openingdate) AS month,
 | 
			
		||||
            COUNT(ap.id) AS count
 | 
			
		||||
            FROM chill_person_accompanying_period ap
 | 
			
		||||
            WHERE ap.user_id = :userId
 | 
			
		||||
            GROUP BY DATE_TRUNC('month', ap.openingdate)
 | 
			
		||||
            ORDER BY DATE_TRUNC('month', ap.openingdate) ASC
 | 
			
		||||
        ";
 | 
			
		||||
 | 
			
		||||
        $rsm = new ResultSetMapping();
 | 
			
		||||
        $rsm->addScalarResult('month', 'month');
 | 
			
		||||
        $rsm->addScalarResult('count', 'count');
 | 
			
		||||
 | 
			
		||||
        $query = $this->entityManager->createNativeQuery($sql, $rsm);
 | 
			
		||||
        $query->setParameter('userId', $userId);
 | 
			
		||||
 | 
			
		||||
        $results = $query->getResult();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $counts = [];
 | 
			
		||||
        $months =[] ;
 | 
			
		||||
        foreach ($results as $result) {
 | 
			
		||||
            $date = new \DateTime($result['month']);
 | 
			
		||||
            $formattedMonth = $date->format('Y-m');
 | 
			
		||||
 | 
			
		||||
            $months[] = $formattedMonth;
 | 
			
		||||
            $counts[] = $result['count'];
 | 
			
		||||
        }
 | 
			
		||||
        return [
 | 
			
		||||
            'months'=>$months,
 | 
			
		||||
            'counts'=>$counts,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								src/Bundle/ChillMainBundle/Widget/Widgets/WidgetBar.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/Bundle/ChillMainBundle/Widget/Widgets/WidgetBar.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Widget\Widgets;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Repository\NotificationRepository;
 | 
			
		||||
use Chill\MainBundle\Widget\WidgetHandlerInterface;
 | 
			
		||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Chill\MainBundle\Widget\Widgets\DataFetcher\WidgetBarDataFetcher;
 | 
			
		||||
 | 
			
		||||
class WidgetBar implements WidgetHandlerInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    private Security $security;
 | 
			
		||||
 | 
			
		||||
    //private AccompanyingPeriodRepository $acpRepository;
 | 
			
		||||
 | 
			
		||||
    private WidgetBarDataFetcher $dataFetcher;
 | 
			
		||||
 | 
			
		||||
    //private NotificationRepository $notificationRepository;
 | 
			
		||||
 | 
			
		||||
    //private EntityManagerInterface $em;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
    //    AccompanyingPeriodRepository $accompanyingPeriodRepository,
 | 
			
		||||
        Security                     $security,
 | 
			
		||||
    //    NotificationRepository       $notificationRepository,
 | 
			
		||||
    //    EntityManagerInterface       $em,
 | 
			
		||||
        WidgetBarDataFetcher         $dataFetcher
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
    //    $this->acpRepository = $accompanyingPeriodRepository;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
    //    $this->notificationRepository = $notificationRepository;
 | 
			
		||||
    //    $this->em = $em;
 | 
			
		||||
        $this->dataFetcher = $dataFetcher;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supports(array $config, array $context = []): bool
 | 
			
		||||
    {
 | 
			
		||||
        // Check if the context is "bar"
 | 
			
		||||
        return $config['alias'] === 'bar';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws \Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function getDataForWidget(array $config, array $context = []): array
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->security->getUser();
 | 
			
		||||
        //$userId = $user->getId();
 | 
			
		||||
        //Hardcoder pour résultat
 | 
			
		||||
        $userId = 19;
 | 
			
		||||
        $dataForWidget = [];
 | 
			
		||||
        if (!$user instanceof User) {
 | 
			
		||||
            throw new AccessDeniedException();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($context !== []) {
 | 
			
		||||
            switch ($context['what']) {
 | 
			
		||||
                case 'accompanying_period_by_month':
 | 
			
		||||
 | 
			
		||||
                    $data = $this->dataFetcher->fetchDataForWidget($userId);
 | 
			
		||||
                    $dataForWidget = [
 | 
			
		||||
                        'labels' => $data['months'],
 | 
			
		||||
                        'datasets' => [
 | 
			
		||||
                            [
 | 
			
		||||
                                'label' => 'Number of accompanying periods opened',
 | 
			
		||||
                                'backgroundColor' => ['#41B883'],
 | 
			
		||||
                                'data' => $data['counts']
 | 
			
		||||
                            ]
 | 
			
		||||
                        ]
 | 
			
		||||
                    ];
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new \InvalidArgumentException('Invalid Context.');
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return [
 | 
			
		||||
            'data' => $dataForWidget,
 | 
			
		||||
            'options' => [
 | 
			
		||||
 | 
			
		||||
                'responsive' => 'true',
 | 
			
		||||
                'maintainAspectRatio' => 'false',
 | 
			
		||||
                'position' => 'relative'
 | 
			
		||||
 | 
			
		||||
            ],
 | 
			
		||||
            'type' => 'bar',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								src/Bundle/ChillMainBundle/Widget/Widgets/WidgetNumber.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/Bundle/ChillMainBundle/Widget/Widgets/WidgetNumber.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Widget\Widgets;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Repository\NotificationRepository;
 | 
			
		||||
use Chill\MainBundle\Widget\WidgetHandlerInterface;
 | 
			
		||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
 | 
			
		||||
use Chill\PersonBundle\Repository\Person;
 | 
			
		||||
use DateInterval;
 | 
			
		||||
use DateTimeImmutable;
 | 
			
		||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
 | 
			
		||||
class WidgetNumber implements WidgetHandlerInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    private Security $security;
 | 
			
		||||
 | 
			
		||||
    //private AccompanyingPeriodRepository $acpRepository;
 | 
			
		||||
 | 
			
		||||
    private NotificationRepository $notificationRepository;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
    //    AccompanyingPeriodRepository $accompanyingPeriodRepository,
 | 
			
		||||
        Security                     $security,
 | 
			
		||||
        NotificationRepository       $notificationRepository,
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
    //    $this->acpRepository = $accompanyingPeriodRepository;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
        $this->notificationRepository = $notificationRepository;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supports(array $config, array $context = []): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $config['alias'] === 'number';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getDataForWidget(array $config, array $context = []): array
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->security->getUser();
 | 
			
		||||
        $dataForWidget = [];
 | 
			
		||||
        if (!$user instanceof User) {
 | 
			
		||||
            throw new AccessDeniedException();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($context !== []) {
 | 
			
		||||
            switch ($context['what']) {
 | 
			
		||||
 | 
			
		||||
                /*case 'accompanying_period_history' :
 | 
			
		||||
                    //Send back a data that need to be serialize in order to see
 | 
			
		||||
                    $since = (new DateTimeImmutable('now'))->sub(new DateInterval('P1Y'));
 | 
			
		||||
                    $data = $this->acpRepository->countByRecentUserHistory($user, $since);
 | 
			
		||||
                    $what = $this->acpRepository->findConfirmedByUser($user);
 | 
			
		||||
                    break;*/
 | 
			
		||||
                case 'notification_sender':
 | 
			
		||||
                    //Count the number of notification the sender send
 | 
			
		||||
                    $countSend = $this->notificationRepository->countAllForSender($user);
 | 
			
		||||
                    $dataForWidget = [
 | 
			
		||||
                        'labels' => ['notification(s) envoyée(s)'],
 | 
			
		||||
                        'datasets' => [
 | 
			
		||||
                            [
 | 
			
		||||
                                'data' => [$countSend]
 | 
			
		||||
                            ]
 | 
			
		||||
                        ]
 | 
			
		||||
                    ];
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case 'notification_unread':
 | 
			
		||||
                    //Count the number of unread notification by the current User
 | 
			
		||||
                    $countUnread = $this->notificationRepository->countUnreadByUser($user);
 | 
			
		||||
                    $dataForWidget = [
 | 
			
		||||
                        'labels' => ['notification(s) non lue(s)'],
 | 
			
		||||
                        'datasets' => [
 | 
			
		||||
                            [
 | 
			
		||||
                                'data' => [$countUnread]
 | 
			
		||||
                            ]
 | 
			
		||||
                        ]
 | 
			
		||||
                    ];
 | 
			
		||||
                    break;
 | 
			
		||||
                default : throw new \InvalidArgumentException('Invalid Context.');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'data' => $dataForWidget,
 | 
			
		||||
            'type' => 'number',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -10,6 +10,22 @@ servers:
 | 
			
		||||
 | 
			
		||||
components:
 | 
			
		||||
    schemas:
 | 
			
		||||
        SavedExport:
 | 
			
		||||
            type: object
 | 
			
		||||
            properties:
 | 
			
		||||
                id:
 | 
			
		||||
                    type: integer
 | 
			
		||||
                user_id:
 | 
			
		||||
                    type: integer
 | 
			
		||||
                description:
 | 
			
		||||
                    type: string
 | 
			
		||||
                exportalias:
 | 
			
		||||
                    type: string
 | 
			
		||||
                options:
 | 
			
		||||
                    type: string #TODO -> je pense que c'est object
 | 
			
		||||
                title:
 | 
			
		||||
                    type: string
 | 
			
		||||
 | 
			
		||||
        User:
 | 
			
		||||
            type: object
 | 
			
		||||
            properties:
 | 
			
		||||
@@ -791,7 +807,7 @@ paths:
 | 
			
		||||
    /1.0/main/civility.json:
 | 
			
		||||
        get:
 | 
			
		||||
            tags:
 | 
			
		||||
            - civility
 | 
			
		||||
                - civility
 | 
			
		||||
            summary: Return all civility types
 | 
			
		||||
            responses:
 | 
			
		||||
                200:
 | 
			
		||||
@@ -843,3 +859,27 @@ paths:
 | 
			
		||||
                403:
 | 
			
		||||
                    description: "Unauthorized"
 | 
			
		||||
 | 
			
		||||
    /1.0/main/saved-export/{id}.json:
 | 
			
		||||
        get:
 | 
			
		||||
            tags:
 | 
			
		||||
                - export
 | 
			
		||||
            summary: Return a specific saved export who is saved by an existing user.
 | 
			
		||||
            parameters:
 | 
			
		||||
            -   name: id
 | 
			
		||||
                in: path
 | 
			
		||||
                required: true
 | 
			
		||||
                description: The saved export id
 | 
			
		||||
                schema:
 | 
			
		||||
                    type: string
 | 
			
		||||
                    format: string
 | 
			
		||||
            responses:
 | 
			
		||||
                200:
 | 
			
		||||
                    description: "ok"
 | 
			
		||||
                    content:
 | 
			
		||||
                        application/json:
 | 
			
		||||
                            schema:
 | 
			
		||||
                                type: string
 | 
			
		||||
                                items:
 | 
			
		||||
                                    $ref: '#/components/schemas/SavedExport'
 | 
			
		||||
                403:
 | 
			
		||||
                    description: "Unauthorized"
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,24 @@ services:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Widget\:
 | 
			
		||||
        resource: '../Widget/'
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Widget\WidgetHandlerManager:
 | 
			
		||||
        arguments:
 | 
			
		||||
            $handlers: !tagged_iterator chill_main.widget_handler
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Widget\Widgets\WidgetNumber:
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        autowire: true
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Widget\Widgets\WidgetBar:
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        autowire: true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Cron\CronManager:
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        autowire: true
 | 
			
		||||
 
 | 
			
		||||
@@ -112,4 +112,17 @@ final class AccompanyingPeriodRepository implements ObjectRepository
 | 
			
		||||
 | 
			
		||||
        return $qb;
 | 
			
		||||
    }
 | 
			
		||||
    public function countByUserGroupedByMonth(User $user): array
 | 
			
		||||
    {
 | 
			
		||||
        $qb = $this->createQueryBuilder('a');
 | 
			
		||||
 | 
			
		||||
        $qb->select()
 | 
			
		||||
            ->addSelect('COUNT(a.id) AS count')
 | 
			
		||||
            ->andWhere('a.user = :user')
 | 
			
		||||
            ->setParameter('user', $user)
 | 
			
		||||
            ->groupBy('month')
 | 
			
		||||
            ->orderBy('month', 'ASC');
 | 
			
		||||
 | 
			
		||||
        return $qb->getQuery()->getResult();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user