Skip to content

Commit 4151d9f

Browse files
authored
Merge pull request #2 from Orbitale/chart
Start working on charts system
2 parents aabf9dd + f74f931 commit 4151d9f

File tree

12 files changed

+303
-19
lines changed

12 files changed

+303
-19
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ ADMIN_PASSWORD='$argon2id$v=19$m=65536,t=4,p=1$N0R4Zi5hUWQ3QXB0bjVGdg$VsVcHzGRfG
7979
Feel free to contribute 😉.
8080

8181
* Make many analytics dashboards (that's what this app is for in the first place, probably with Highcharts).
82+
* Support JS closures in Chart objects (by using a placeholder to remove quotes maybe?).
8283
* Add a lot of fixtures to play with.
8384
* Add translations for tags (maybe using an extension like gedmo or knp?).
8485
* Implement more source file types like xls, ods, etc., that could be transformed to CSV before importing them. [PHPSpreadsheet](https://phpspreadsheet.readthedocs.io/) is already installed, though not used yet.
@@ -97,9 +98,10 @@ Feel free to contribute 😉.
9798

9899
* Operation tags (insurance, internet provider, car loan, etc.). Multiple tags per operation.
99100
* Demo app at https://piers.ovh/compotes/ with credentials `admin`/`admin` and database reset every day.
100-
* Added default tags (in French only for now)
101-
* Docker setup with Compose
102-
* Added tons of other commands to the Makefile
101+
* Added default tags (in French only for now).
102+
* Docker setup with Compose.
103+
* Added tons of other commands to the Makefile.
104+
* Made a first PoC for the analytics dashboard.
103105

104106
# License
105107

assets/js/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import '../css/app.css';
22
import * as Highcharts from "highcharts";
33

4-
// import $ from 'jquery';
4+
require('../css/app.css');
55

6-
console.info('App startup');
6+
global.Highcharts = Highcharts;

config/packages/easy_admin.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ easy_admin:
88
- { icon: sync, label: "Sync operations", route: apply_rules }
99

1010
- label: ''
11-
- { icon: ruler-vertical, entity: TagRule }
12-
- { icon: tags, entity: Tag }
11+
- { icon: money-check, entity: Operation }
12+
- { icon: chart-bar, label: "Analytics", route: analytics }
1313

1414
- label: ''
15-
- { icon: money-check, entity: Operation }
15+
- { icon: ruler-vertical, entity: TagRule }
16+
- { icon: tags, entity: Tag }
1617

1718
entities:
1819
TagRule:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Compotes package.
7+
*
8+
* (c) Alex "Pierstoval" Rock <[email protected]>.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace App\Controller;
15+
16+
use App\Highcharts\Chart\TagUsageChart;
17+
use App\Repository\OperationRepository;
18+
use App\Repository\TagRepository;
19+
use Symfony\Component\HttpFoundation\Response;
20+
use Symfony\Component\Routing\Annotation\Route;
21+
use Twig\Environment;
22+
23+
class AnalyticsController
24+
{
25+
private Environment $twig;
26+
private TagRepository $tagRepository;
27+
private OperationRepository $operationRepository;
28+
29+
public function __construct(
30+
Environment $twig,
31+
TagRepository $tagRepository,
32+
OperationRepository $operationRepository
33+
) {
34+
$this->twig = $twig;
35+
$this->tagRepository = $tagRepository;
36+
$this->operationRepository = $operationRepository;
37+
}
38+
39+
/**
40+
* @Route("/admin/analytics", name="analytics")
41+
*/
42+
public function analytics(): Response
43+
{
44+
$operations = $this->operationRepository->findWithTags();
45+
46+
return new Response($this->twig->render('analytics.html.twig', [
47+
'charts' => [
48+
TagUsageChart::create($operations),
49+
],
50+
]));
51+
}
52+
}

src/DataFixtures/TagFixtures.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected function getObjects(): array
5151
['name' => 'Cheque-recu', 'parent' => $getParentClosure($earningsParent)],
5252
['name' => 'Dividendes', 'parent' => $getParentClosure($earningsParent)],
5353
['name' => 'Deblocage-emprunt', 'parent' => $getParentClosure($earningsParent)],
54-
['name' => 'Depôt-argent', 'parent' => $getParentClosure($earningsParent)],
54+
['name' => 'Depot-argent', 'parent' => $getParentClosure($earningsParent)],
5555
['name' => 'Interets', 'parent' => $getParentClosure($earningsParent)],
5656
['name' => 'Loyers', 'parent' => $getParentClosure($earningsParent)],
5757
['name' => 'Pensions', 'parent' => $getParentClosure($earningsParent)],
@@ -96,12 +96,12 @@ protected function getObjects(): array
9696
['name' => 'Pension-alimentaire', 'parent' => $getParentClosure($parent)],
9797
['name' => 'Scolarite-etudes', 'parent' => $getParentClosure($parent)],
9898

99-
['name' => $parent = 'Impôts-et-taxes', 'parent' => $getParentClosure($expensesParent)],
99+
['name' => $parent = 'Impots-et-taxes', 'parent' => $getParentClosure($expensesParent)],
100100
['name' => 'Amendes', 'parent' => $getParentClosure($parent)],
101-
['name' => 'Contributions-sociales-(csg-crds)', 'parent' => $getParentClosure($parent)],
102-
['name' => 'Impôt-sur-la-fortune', 'parent' => $getParentClosure($parent)],
103-
['name' => 'Impôt-sur-le-revenu', 'parent' => $getParentClosure($parent)],
104-
['name' => 'Impôts-et-taxes-autres', 'parent' => $getParentClosure($parent)],
101+
['name' => 'Contributions-sociales-csg-crds', 'parent' => $getParentClosure($parent)],
102+
['name' => 'Impot-sur-la-fortune', 'parent' => $getParentClosure($parent)],
103+
['name' => 'Impot-sur-le-revenu', 'parent' => $getParentClosure($parent)],
104+
['name' => 'Impots-et-taxes-autres', 'parent' => $getParentClosure($parent)],
105105
['name' => 'Taxe-habitation', 'parent' => $getParentClosure($parent)],
106106
['name' => 'Taxe-fonciere', 'parent' => $getParentClosure($parent)],
107107

@@ -135,7 +135,7 @@ protected function getObjects(): array
135135

136136
['name' => $parent = 'Transports-et-vehicules', 'parent' => $getParentClosure($expensesParent)],
137137
['name' => 'Assurance-vehicule', 'parent' => $getParentClosure($parent)],
138-
['name' => 'Billet-avion, billet de train', 'parent' => $getParentClosure($parent)],
138+
['name' => 'Billet-avion-ou-train', 'parent' => $getParentClosure($parent)],
139139
['name' => 'Carburant', 'parent' => $getParentClosure($parent)],
140140
['name' => 'Credit-auto', 'parent' => $getParentClosure($parent)],
141141
['name' => 'Entretien-vehicule', 'parent' => $getParentClosure($parent)],
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Compotes package.
7+
*
8+
* (c) Alex "Pierstoval" Rock <[email protected]>.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace App\Highcharts\Chart;
15+
16+
abstract class AbstractChart implements ChartInterface
17+
{
18+
public function getConfig(): array
19+
{
20+
return $this->getOptions() + ['series' => $this->getSeries()];
21+
}
22+
23+
abstract protected function getSeries(): array;
24+
25+
abstract protected function getOptions(): array;
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Compotes package.
7+
*
8+
* (c) Alex "Pierstoval" Rock <[email protected]>.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace App\Highcharts\Chart;
15+
16+
interface ChartInterface
17+
{
18+
public function getName(): string;
19+
20+
/**
21+
* This corresponds to the options sent to the Highcharts js object.
22+
*
23+
* Config has to be convertible to JSON or JS.
24+
* If your config contains closures, it will be rendered as a string (for now).
25+
*
26+
* @see https://api.highcharts.com/highcharts/
27+
*/
28+
public function getConfig(): array;
29+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Compotes package.
7+
*
8+
* (c) Alex "Pierstoval" Rock <[email protected]>.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace App\Highcharts\Chart;
15+
16+
use App\Entity\Operation;
17+
18+
class TagUsageChart extends AbstractChart
19+
{
20+
/** * @var Operation[] */
21+
private array $operations = [];
22+
23+
private function __construct()
24+
{
25+
}
26+
27+
public function getName(): string
28+
{
29+
return 'Tags';
30+
}
31+
32+
public static function create($operations): self
33+
{
34+
$self = new self();
35+
36+
foreach ($operations as $operation) {
37+
$self->addOperation($operation);
38+
}
39+
40+
return $self;
41+
}
42+
43+
protected function getOptions(): array
44+
{
45+
return [
46+
'chart' => [
47+
'type' => $type = 'bar',
48+
'height' => 500,
49+
],
50+
'legend' => [
51+
'align' => 'right',
52+
'layout' => 'vertical',
53+
],
54+
'title' => ['text' => 'Tags usage'],
55+
'xAxis' => [
56+
'categories' => ['Tags'],
57+
],
58+
'yAxis' => [
59+
'title' => ['text' => 'Number of operations with these tags'],
60+
],
61+
'plotOptions' => [
62+
$type => [
63+
'pointWidth' => 10,
64+
'borderWidth' => 0,
65+
'groupPadding' => 0.01,
66+
],
67+
],
68+
];
69+
}
70+
71+
protected function getSeries(): array
72+
{
73+
$series = [];
74+
75+
foreach ($this->operations as $operation) {
76+
foreach ($operation->getTags() as $tag) {
77+
$tagName = $tag->getName();
78+
if (!isset($series[$tagName])) {
79+
$series[$tagName] = [
80+
'name' => $tagName,
81+
'data' => [0],
82+
];
83+
}
84+
85+
$series[$tagName]['data'][0]++;
86+
}
87+
}
88+
89+
\ksort($series);
90+
91+
return \array_values($series);
92+
}
93+
94+
private function addOperation(Operation $operation): void
95+
{
96+
$this->operations[] = $operation;
97+
}
98+
}

src/Repository/OperationRepository.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,20 @@ public function monthIsPopulated(DateTimeImmutable $month): bool
5858

5959
return $count > 0;
6060
}
61+
62+
/**
63+
* @return Operation[]
64+
*/
65+
public function findWithTags(): array
66+
{
67+
return $this->_em->createQuery(
68+
<<<DQL
69+
SELECT operation, tags
70+
FROM {$this->_entityName} as operation
71+
LEFT JOIN operation.tags as tags
72+
DQL
73+
)
74+
->getResult()
75+
;
76+
}
6177
}

src/Twig/SlugifyExtension.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Compotes package.
7+
*
8+
* (c) Alex "Pierstoval" Rock <[email protected]>.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace App\Twig;
15+
16+
use Symfony\Component\String\Slugger\SluggerInterface;
17+
use Twig\Extension\AbstractExtension;
18+
use Twig\TwigFilter;
19+
20+
class SlugifyExtension extends AbstractExtension
21+
{
22+
private SluggerInterface $slugger;
23+
24+
public function __construct(SluggerInterface $slugger)
25+
{
26+
$this->slugger = $slugger;
27+
}
28+
29+
public function getFilters()
30+
{
31+
return [
32+
new TwigFilter('slug', [$this, 'slugify']),
33+
];
34+
}
35+
36+
public function slugify($string): string
37+
{
38+
if (\is_object($string)) {
39+
$string = (string) $string;
40+
}
41+
42+
return $this->slugger->slug($string)->toString();
43+
}
44+
}

0 commit comments

Comments
 (0)