From f8bde4345f3c00836dc300cd51aa14f3701319ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Thu, 27 Jul 2023 15:23:53 +0000 Subject: [PATCH] First basic statistics graph generation using Chart.js --- .../Admin/ContactsListController.php | 10 +- .../Admin/StatisticsController.php | 224 +++++++++++++++--- flexiapi/app/Libraries/StatisticsCruncher.php | 196 --------------- flexiapi/composer.lock | 68 +++--- .../factories/StatisticsMessageFactory.php | 18 ++ ...raints_and_statistics_messages_indexes.php | 45 ++++ .../seeds/StatisticsMessagesSeeder.php} | 32 ++- flexiapi/public/css/far.css | 8 + .../account/documentation_markdown.blade.php | 2 +- .../views/account/register/email.blade.php | 109 +++++---- .../views/account/register/phone.blade.php | 9 +- .../views/admin/account/create_edit.blade.php | 28 +-- .../admin/contacts_list/create_edit.blade.php | 25 +- .../views/admin/statistics/show.blade.php | 47 ++++ .../views/admin/statistics/show_day.blade.php | 37 --- .../admin/statistics/show_month.blade.php | 37 --- .../admin/statistics/show_week.blade.php | 37 --- .../api/documentation_markdown.blade.php | 14 -- .../resources/views/layouts/main.blade.php | 3 + .../resources/views/parts/graph.blade.php | 27 +++ .../resources/views/parts/sidebar.blade.php | 2 +- flexiapi/resources/views/parts/tabs.blade.php | 2 +- .../views/parts/tabs/register.blade.php | 4 +- flexiapi/routes/api.php | 4 - flexiapi/routes/web.php | 12 +- 25 files changed, 496 insertions(+), 504 deletions(-) delete mode 100644 flexiapi/app/Libraries/StatisticsCruncher.php create mode 100644 flexiapi/database/factories/StatisticsMessageFactory.php create mode 100644 flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php rename flexiapi/{app/Http/Controllers/Api/StatisticController.php => database/seeds/StatisticsMessagesSeeder.php} (58%) create mode 100644 flexiapi/resources/views/admin/statistics/show.blade.php delete mode 100644 flexiapi/resources/views/admin/statistics/show_day.blade.php delete mode 100644 flexiapi/resources/views/admin/statistics/show_month.blade.php delete mode 100644 flexiapi/resources/views/admin/statistics/show_week.blade.php create mode 100644 flexiapi/resources/views/parts/graph.blade.php diff --git a/flexiapi/app/Http/Controllers/Admin/ContactsListController.php b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php index 667fc38..d995e6a 100644 --- a/flexiapi/app/Http/Controllers/Admin/ContactsListController.php +++ b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php @@ -21,6 +21,7 @@ namespace App\Http\Controllers\Admin; use App\ContactsList; use App\Http\Controllers\Controller; +use Illuminate\Validation\Rule; use Illuminate\Http\Request; class ContactsListController extends Controller @@ -49,8 +50,7 @@ class ContactsListController extends Controller public function store(Request $request) { $request->validate([ - 'title' => 'required', - 'description' => 'required' + 'title' => 'required|unique:contacts_lists' ]); $contactsList = new ContactsList; @@ -71,8 +71,10 @@ class ContactsListController extends Controller public function update(Request $request, int $id) { $request->validate([ - 'title' => 'required', - 'description' => 'required' + 'title' => [ + 'required', + Rule::unique('contacts_lists')->ignore($id), + ], ]); $contactsList = ContactsList::findOrFail($id); diff --git a/flexiapi/app/Http/Controllers/Admin/StatisticsController.php b/flexiapi/app/Http/Controllers/Admin/StatisticsController.php index d81d76d..925f711 100644 --- a/flexiapi/app/Http/Controllers/Admin/StatisticsController.php +++ b/flexiapi/app/Http/Controllers/Admin/StatisticsController.php @@ -2,52 +2,218 @@ namespace App\Http\Controllers\Admin; +use App\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; -use App\Libraries\StatisticsCruncher; +use App\StatisticsMessage; +use Carbon\Carbon; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; class StatisticsController extends Controller { - public function showDay(Request $request) + public function index(Request $request) { - $day = StatisticsCruncher::day(); - $maxDay = 0; - foreach ($day as $hour) { - if ($maxDay < $hour['all']) $maxDay = $hour['all']; - } - - return view('admin.statistics.show_day', [ - 'day' => $day, - 'max_day' => $maxDay, + return redirect()->route('admin.statistics.show', [ + 'type' => 'messages' ]); } - public function showWeek(Request $request) + public function edit(Request $request) { - $week = StatisticsCruncher::week(); - $maxWeek = 0; - foreach ($week as $day) { - if ($maxWeek < $day['all']) $maxWeek = $day['all']; - } - - return view('admin.statistics.show_week', [ - 'week' => $week, - 'max_week' => $maxWeek, + return redirect()->route('admin.statistics.show', [ + 'from' => $request->get('from'), + 'type' => $request->get('type'), + 'to' => $request->get('to'), + 'by' => $request->get('by'), ]); } - public function showMonth(Request $request) + public function show(Request $request, string $type = 'messages') { - $month = StatisticsCruncher::month(); - $maxMonth = 0; - foreach ($month as $day) { - if ($maxMonth < $day['all']) $maxMonth = $day['all']; + $request->validate([ + 'from' => 'date_format:Y-m-d|before:to', + 'to' => 'date_format:Y-m-d|after:from', + 'by' => 'in:day,week,month,year', + ]); + + $dateColumn = 'created_at'; + $label = 'Label'; + + switch ($type) { + case 'messages': + $dateColumn = 'sent_at'; + $label = 'Messages'; + $data = StatisticsMessage::orderBy($dateColumn, 'asc'); + break; + + case 'accounts': + $label = 'Accounts'; + $data = Account::orderBy($dateColumn, 'asc'); + break; } - return view('admin.statistics.show_month', [ - 'month' => $month, - 'max_month' => $maxMonth, + $data = $data->groupBy('moment') + ->orderBy('moment', 'desc') + ->setEagerLoads([]); + + if ($request->get('to')) { + $data = $data->where($dateColumn, '<=', $request->get('to')); + } + + $by = $request->get('by', 'day'); + + switch ($by) { + case 'day': + $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subDay()->format('Y-m-d H:i:s'))) + ->get([ + DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d %H') as moment"), + DB::raw('COUNT(*) as "count"') + ]); + break; + case 'week': + $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subWeek()->format('Y-m-d H:i:s'))) + ->get([ + DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"), + DB::raw('COUNT(*) as "count"') + ]); + break; + case 'month': + $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subMonth()->format('Y-m-d H:i:s'))) + ->get([ + DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"), + DB::raw('COUNT(*) as "count"') + ]); + break; + case 'year': + $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subYear()->format('Y-m-d H:i:s'))) + ->get([ + DB::raw("date_format(" . $dateColumn . ",'%Y-%m') as moment"), + DB::raw('COUNT(*) as "count"') + ]); + break; + } + + $data = $data->each->setAppends([])->pluck('count', 'moment'); + + $data = $this->compileStatistics( + $by, + $request->get('from'), + $request->get('to'), + $data + ); + + if ($request->get('export', false)) { + $file = fopen('php://output', 'w'); + + $callback = function () use ($data, $file) { + foreach ($data as $key => $value) { + fputcsv($file, [$key, $value]); + } + + fclose($file); + }; + + return response()->stream($callback, 200, [ + "Content-type" => "text/csv", + "Content-Disposition" => "attachment; filename=export.csv", + "Pragma" => "no-cache", + "Cache-Control" => "must-revalidate, post-check=0, pre-check=0", + "Expires" => "0" + ]); + } + + $config = [ + 'type' => 'bar', + 'data' => [ + 'labels' => $data->keys()->toArray(), + 'datasets' => [[ + 'label' => $label, + 'borderColor' => 'rgba(108, 122, 135, 1)', + 'backgroundColor' => 'rgba(108, 122, 135, 1)', + 'data' => $data->values()->toArray(), + 'order' => 1 + ]] + ], + 'options' => [ + 'maintainAspectRatio' => false, + 'spanGaps' => true, + 'legend' => [ + 'position' => 'right' + ], + 'scales' => [ + 'y' => [ + 'stacked' => true, + 'title' => [ + 'display' => true, + 'text' => $label + ] + ], + 'x' => [ + 'stacked' => true, + ] + ], + 'interaction' => [ + 'mode' => 'nearest', + 'axis' => 'x', + 'intersect' => false + ], + ] + ]; + + return view('admin.statistics.show', [ + 'jsonConfig' => json_encode($config), + 'by' => $by, + 'type' => $type, + 'request' => $request ]); } + + private static function compileStatistics(string $by, $from, $to, $data): Collection + { + $stats = []; + + switch ($by) { + case 'day': + $period = collect(CarbonInterval::hour()->toPeriod( + $from ?? Carbon::now()->subDay()->format('Y-m-d H:i:s'), + $to ?? Carbon::now()->format('Y-m-d H:i:s') + ))->map->format('Y-m-d H'); + break; + + case 'week': + $period = collect(CarbonPeriod::create( + $from ?? Carbon::now()->subWeek(), + $to ?? Carbon::now() + ))->map->format('Y-m-d'); + break; + + case 'month': + $period = collect( + CarbonPeriod::create( + $from ?? Carbon::now()->subMonth(), + $to ?? Carbon::now() + ) + )->map->format('Y-m-d'); + break; + + case 'year': + $period = collect( + CarbonPeriod::create( + $from ?? Carbon::now()->subYear(), + $to ?? Carbon::now() + ) + )->map->format('Y-m'); + break; + } + + foreach ($period as $moment) { + $stats[$moment] = $data[$moment] ?? 0; + } + + return collect($stats); + } } diff --git a/flexiapi/app/Libraries/StatisticsCruncher.php b/flexiapi/app/Libraries/StatisticsCruncher.php deleted file mode 100644 index 3e7ac09..0000000 --- a/flexiapi/app/Libraries/StatisticsCruncher.php +++ /dev/null @@ -1,196 +0,0 @@ -. -*/ - -namespace App\Libraries; - -use Illuminate\Support\Facades\DB; - -use Carbon\Carbon; -use Carbon\CarbonPeriod; -use Carbon\CarbonInterval; - -use App\Account; - -class StatisticsCruncher -{ - public static function month() - { - $data = self::getAccountFrom(Carbon::now()->subMonth()) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliases = self::getAccountFrom(Carbon::now()->subMonth()) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataActivated = self::getAccountFrom(Carbon::now()->subMonth()) - ->where('activated', true) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subMonth()) - ->where('activated', true) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - return self::compileStatistics( - collect(CarbonPeriod::create(Carbon::now()->subMonth(), Carbon::now()))->map->format('Y-m-d'), - $data, - $dataAliases, - $dataActivated, - $dataAliasesActivated - ); - } - - public static function week() - { - $data = self::getAccountFrom(Carbon::now()->subWeek()) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliases = self::getAccountFrom(Carbon::now()->subWeek()) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataActivated = self::getAccountFrom(Carbon::now()->subWeek()) - ->where('activated', true) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subWeek()) - ->where('activated', true) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - return self::compileStatistics( - collect(CarbonPeriod::create(Carbon::now()->subWeek(), Carbon::now()))->map->format('Y-m-d'), - $data, - $dataAliases, - $dataActivated, - $dataAliasesActivated - ); - } - - public static function day() - { - $data = self::getAccountFrom(Carbon::now()->subDay()) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliases = self::getAccountFrom(Carbon::now()->subDay()) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataActivated = self::getAccountFrom(Carbon::now()->subDay()) - ->where('activated', true) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subDay()) - ->where('activated', true) - ->whereIn('id', function ($query) { - $query->select('account_id') - ->from('aliases'); - }) - ->get(array( - DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"), - DB::raw('COUNT(*) as "count"') - ))->each->setAppends([])->pluck('count', 'moment'); - - return self::compileStatistics( - collect(CarbonInterval::hour()->toPeriod(Carbon::now()->subDay(), Carbon::now()))->map->format('Y-m-d H'), - $data, - $dataAliases, - $dataActivated, - $dataAliasesActivated - ); - } - - private static function getAccountFrom($date) - { - return Account::where('created_at', '>=', $date) - ->groupBy('moment') - ->orderBy('moment', 'DESC') - ->setEagerLoads([]); - } - - private static function compileStatistics($period, $data, $dataAliases, $dataActivated, $dataAliasesActivated) - { - $stats = []; - - foreach ($period as $moment) { - $all = $data[$moment] ?? 0; - $aliases = $dataAliases[$moment] ?? 0; - $activated = $dataActivated[$moment] ?? 0; - $activatedAliases = $dataAliasesActivated[$moment] ?? 0; - - $stats[$moment] = [ - 'all' => $all, - 'phone' => $aliases, - 'email' => $all - $aliases, - 'activated_phone' => $activatedAliases, - 'activated_email' => $activated - $activatedAliases - ]; - } - - return $stats; - } -} diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock index 9546ec0..9c0ee84 100644 --- a/flexiapi/composer.lock +++ b/flexiapi/composer.lock @@ -399,16 +399,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.4", + "version": "3.6.5", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" + "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/96d5a70fd91efdcec81fc46316efc5bf3da17ddf", + "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf", "shasum": "" }, "require": { @@ -423,10 +423,10 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.14", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.21", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.7", + "phpunit/phpunit": "9.6.9", "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", @@ -491,7 +491,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.4" + "source": "https://github.com/doctrine/dbal/tree/3.6.5" }, "funding": [ { @@ -507,7 +507,7 @@ "type": "tidelift" } ], - "time": "2023-06-15T07:40:12+00:00" + "time": "2023-07-17T09:15:50+00:00" }, { "name": "doctrine/deprecations", @@ -1799,16 +1799,16 @@ }, { "name": "laravel/framework", - "version": "v9.52.10", + "version": "v9.52.12", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "858add225ce88a76c43aec0e7866288321ee0ee9" + "reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9", - "reference": "858add225ce88a76c43aec0e7866288321ee0ee9", + "url": "https://api.github.com/repos/laravel/framework/zipball/8bfd22be79f437fa335e70692e4e91ff40ce561d", + "reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d", "shasum": "" }, "require": { @@ -1993,20 +1993,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-06-27T13:25:54+00:00" + "time": "2023-07-26T13:20:55+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902", + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902", "shasum": "" }, "require": { @@ -2053,7 +2053,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-01-30T18:31:20+00:00" + "time": "2023-07-14T13:56:28+00:00" }, { "name": "laravelcollective/html", @@ -3434,16 +3434,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -3499,7 +3499,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -3507,7 +3508,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9009,16 +9010,16 @@ }, { "name": "mockery/mockery", - "version": "1.6.3", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d" + "reference": "d1413755e26fe56a63455f7753221c86cbb88f66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/b1be135c1ba7632f0248e07ee5e6e412576a309d", - "reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d", + "url": "https://api.github.com/repos/mockery/mockery/zipball/d1413755e26fe56a63455f7753221c86cbb88f66", + "reference": "d1413755e26fe56a63455f7753221c86cbb88f66", "shasum": "" }, "require": { @@ -9032,16 +9033,17 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.3", "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", "vimeo/psalm": "^5.13.1" }, "type": "library", "autoload": { "files": [ - "src/helpers.php", - "src/Mockery.php" + "library/helpers.php", + "library/Mockery.php" ], "psr-4": { - "Mockery\\": "src/Mockery" + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -9089,7 +9091,7 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2023-07-18T17:47:29+00:00" + "time": "2023-07-19T15:51:02+00:00" }, { "name": "nunomaduro/collision", diff --git a/flexiapi/database/factories/StatisticsMessageFactory.php b/flexiapi/database/factories/StatisticsMessageFactory.php new file mode 100644 index 0000000..3252a96 --- /dev/null +++ b/flexiapi/database/factories/StatisticsMessageFactory.php @@ -0,0 +1,18 @@ + $this->faker->uuid(), + 'from' => $this->faker->email(), + 'sent_at' => $this->faker->dateTimeBetween('-1 year'), + 'encrypted' => false + ]; + } +} diff --git a/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php b/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php new file mode 100644 index 0000000..42e59d5 --- /dev/null +++ b/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php @@ -0,0 +1,45 @@ +unique('title'); + $table->text('description')->nullable(true)->change(); + }); + + Schema::table('statistics_messages', function (Blueprint $table) { + $table->index('sent_at'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->index('created_at'); + }); + } + + public function down() + { + Schema::table('contacts_lists', function (Blueprint $table) { + $table->dropUnique('contacts_lists_title_unique'); + $table->text('description')->nullable(false)->change(); + }); + + Schema::table('statistics_messages', function (Blueprint $table) { + $table->dropIndex('statistics_messages_sent_at_index'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->dropIndex('accounts_created_at_index'); + }); + } +}; diff --git a/flexiapi/app/Http/Controllers/Api/StatisticController.php b/flexiapi/database/seeds/StatisticsMessagesSeeder.php similarity index 58% rename from flexiapi/app/Http/Controllers/Api/StatisticController.php rename to flexiapi/database/seeds/StatisticsMessagesSeeder.php index 0398610..617ce34 100644 --- a/flexiapi/app/Http/Controllers/Api/StatisticController.php +++ b/flexiapi/database/seeds/StatisticsMessagesSeeder.php @@ -1,7 +1,7 @@ . */ -namespace App\Http\Controllers\Api; +namespace Database\Seeders; -use App\Http\Controllers\Controller; -use Illuminate\Http\Request; +use Illuminate\Database\Seeder; -use App\Libraries\StatisticsCruncher; +use App\StatisticsMessage; +use App\StatisticsMessageDevice; +use Illuminate\Support\Facades\Schema; -class StatisticController extends Controller +class StatisticsMessagesSeeder extends Seeder { - public function month(Request $request) + public function run() { - return StatisticsCruncher::month(); - } + Schema::disableForeignKeyConstraints(); + StatisticsMessageDevice::truncate(); + StatisticsMessage::truncate(); + Schema::enableForeignKeyConstraints(); - public function week(Request $request) - { - return StatisticsCruncher::week(); - } - - public function day(Request $request) - { - return StatisticsCruncher::day(); + StatisticsMessage::factory() + ->count(10000) + ->create(); } } diff --git a/flexiapi/public/css/far.css b/flexiapi/public/css/far.css index ca61078..3de339d 100644 --- a/flexiapi/public/css/far.css +++ b/flexiapi/public/css/far.css @@ -637,6 +637,10 @@ table tr.empty td:before { margin: 0; } +.chip.selected { + font-weight: bold; +} + /** Pagination **/ ul.pagination { @@ -703,4 +707,8 @@ select.list_toggle { .disabled { opacity: 0.5; pointer-events: none; +} + +#chart { + min-height: 80vh; } \ No newline at end of file diff --git a/flexiapi/resources/views/account/documentation_markdown.blade.php b/flexiapi/resources/views/account/documentation_markdown.blade.php index efe674b..9ef7291 100644 --- a/flexiapi/resources/views/account/documentation_markdown.blade.php +++ b/flexiapi/resources/views/account/documentation_markdown.blade.php @@ -114,4 +114,4 @@ An adminisator can create, edit and delete account types. Those can be used to c ## Statistics -The statistics panel show registrations statistics based on their type (mobile and email based registration) and their activations states. +The statistics panel show different statistics recorder by the Account Manager, they can be explored, filtered and exported. \ No newline at end of file diff --git a/flexiapi/resources/views/account/register/email.blade.php b/flexiapi/resources/views/account/register/email.blade.php index 2bebb7b..f90f377 100644 --- a/flexiapi/resources/views/account/register/email.blade.php +++ b/flexiapi/resources/views/account/register/email.blade.php @@ -1,74 +1,71 @@ @extends('layouts.main', ['welcome' => true]) @section('content') +
+

+ You already have an account? + Login +

-
+

account_circle Register

-

- You already have an account? - Login -

+ @include('parts.tabs.register') -

account_circle Register

+ {!! Form::open(['route' => 'account.store']) !!} -@include('parts.tabs.register') +
+ {!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!} + {!! Form::label('username', 'Username') !!} + @include('parts.errors', ['name' => 'username']) +
-{!! Form::open(['route' => 'account.store']) !!} +
+ +
-
- {!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!} - {!! Form::label('username', 'Username') !!} - @include('parts.errors', ['name' => 'username']) -
+
+ {!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!} + {!! Form::label('email', 'Email') !!} + @include('parts.errors', ['name' => 'email']) +
+
+ {!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!} + {!! Form::label('email_confirmation', 'Confirm email') !!} + @include('parts.errors', ['name' => 'email_confirmation']) +
-
- -
+
+ + + @include('parts.errors', ['name' => 'password']) +
+
+ + + @include('parts.errors', ['name' => 'password_confirmation']) +
-
- {!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!} - {!! Form::label('email', 'Email') !!} - @include('parts.errors', ['name' => 'email']) -
-
- {!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!} - {!! Form::label('email_confirmation', 'Confirm email') !!} - @include('parts.errors', ['name' => 'email_confirmation']) -
+ @if (!empty(config('app.newsletter_registration_address'))) +
+ {!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!} + +
+ @endif -
- {!! Form::password('password', ['required']) !!} - {!! Form::label('password', 'Password') !!} - @include('parts.errors', ['name' => 'password']) -
-
- {!! Form::password('password_confirmation', ['required']) !!} - {!! Form::label('password_confirmation', 'Confirm password') !!} - @include('parts.errors', ['name' => 'password_confirmation']) -
+ @include('parts.terms') -@if (!empty(config('app.newsletter_registration_address'))) -
- {!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!} - -
-@endif +
+ {!! Form::submit('Register', ['class' => 'btn oppose']) !!} +
-@include('parts.terms') - -
- {!! Form::submit('Register', ['class' => 'btn oppose']) !!} -
- -{!! Form::close() !!} - -
-
- -
+ {!! Form::close() !!} +
+
+ +
@endsection @section('footer') -Hop -@endsection \ No newline at end of file + Hop +@endsection diff --git a/flexiapi/resources/views/account/register/phone.blade.php b/flexiapi/resources/views/account/register/phone.blade.php index e3dfdfe..71b4203 100644 --- a/flexiapi/resources/views/account/register/phone.blade.php +++ b/flexiapi/resources/views/account/register/phone.blade.php @@ -31,13 +31,14 @@
- {!! Form::password('password', ['required']) !!} - {!! Form::label('password', 'Password') !!} + + @include('parts.errors', ['name' => 'password'])
- {!! Form::password('password_confirmation', ['required']) !!} - {!! Form::label('password_confirmation', 'Confirm password') !!} + + + @include('parts.errors', ['name' => 'password_confirmation'])
@include('parts.terms') diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php index 26bf984..0cce434 100644 --- a/flexiapi/resources/views/admin/account/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/create_edit.blade.php @@ -1,18 +1,24 @@ @extends('layouts.main') @section('content') -
- @if ($account->id) - + @if ($account->id) +
+

people Edit an account

+
Cancel + delete Delete -

people Edit an account

-

Updated on {{ $account->updated_at->format('d/m/Y') }} - @else + +

+

Updated on {{ $account->updated_at->format('d/m/Y') }} + @else +

people Create an account

- @endif -
+ Cancel + + + @endif
- -
- -
-
-
@if ($account->id) diff --git a/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php index 51ee613..30d6f89 100644 --- a/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php +++ b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php @@ -4,13 +4,16 @@
@if ($contacts_list->id)

account_box Edit a Contacts List

- + Cancel + delete Delete - + @else

account_box Create a Contacts List

+ Cancel + @endif
@@ -18,20 +21,19 @@

Updated on {{ $contacts_list->updated_at->format('d/m/Y') }} @endif -

@csrf @method($contacts_list->id ? 'put' : 'post')
- + @include('parts.errors', ['name' => 'title'])
- + @include('parts.errors', ['name' => 'description'])
@@ -45,15 +47,15 @@ selected

- + @csrf @method('delete') - +
@@ -79,7 +81,8 @@ @foreach ($contacts_list->contacts as $contact) - + {{ $contact->identifier }} diff --git a/flexiapi/resources/views/admin/statistics/show.blade.php b/flexiapi/resources/views/admin/statistics/show.blade.php new file mode 100644 index 0000000..8f1bfe6 --- /dev/null +++ b/flexiapi/resources/views/admin/statistics/show.blade.php @@ -0,0 +1,47 @@ +@extends('layouts.main') + +@section('content') + @include('parts.tabs', [ + 'items' => [ + route('admin.statistics.show', ['type' => 'messages']) => 'Messages', + route('admin.statistics.show', ['type' => 'accounts']) => 'Accounts', + ], + ]) + +
+
+ @csrf + @method('post') + + + + +
+ + +
+
+ + +
+ +
+ Day + Week + Month + Year +
+ + Reset + + download Export + +
+
+ + @include('parts.graph') +@endsection diff --git a/flexiapi/resources/views/admin/statistics/show_day.blade.php b/flexiapi/resources/views/admin/statistics/show_day.blade.php deleted file mode 100644 index 164d33b..0000000 --- a/flexiapi/resources/views/admin/statistics/show_day.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -@extends('layouts.main') - -@section('breadcrumb') - -@endsection - -@section('content') - - - -

Statistics

- -@include('admin.statistics.parts.legend') - -

Day

- -
- @foreach ($day as $key => $hour) -
- @include('admin.statistics.parts.columns', ['slice' => $hour, 'max' => $max_day]) -
- @endforeach -
- -@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/statistics/show_month.blade.php b/flexiapi/resources/views/admin/statistics/show_month.blade.php deleted file mode 100644 index 7347442..0000000 --- a/flexiapi/resources/views/admin/statistics/show_month.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -@extends('layouts.main') - -@section('breadcrumb') - -@endsection - -@section('content') - - - -

Statistics

- -@include('admin.statistics.parts.legend') - -

Month

- -
-@foreach ($month as $key => $day) -
- @include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_month]) -
-@endforeach -
- -@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/statistics/show_week.blade.php b/flexiapi/resources/views/admin/statistics/show_week.blade.php deleted file mode 100644 index 8fc4a67..0000000 --- a/flexiapi/resources/views/admin/statistics/show_week.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -@extends('layouts.main') - -@section('breadcrumb') - -@endsection - -@section('content') - - - -

Statistics

- -@include('admin.statistics.parts.legend') - -

Week

- -
-@foreach ($week as $key => $day) -
- @include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_week]) -
-@endforeach -
- -@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index a2588d9..98497b2 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -525,20 +525,6 @@ JSON parameters: * `to` required, SIP address of the receiver * `body` required, content of the message -## Statistics - -### `GET /statistics/day` -Admin -Retrieve registrations statistics for 24 hours. - -### `GET /statistics/week` -Admin -Retrieve registrations statistics for a week. - -### `GET /statistics/month` -Admin -Retrieve registrations statistics for a month. - # Non-API Endpoints The following URLs are **not API endpoints** they are not returning `JSON` content and they are not located under `/api` but directly under the root path. diff --git a/flexiapi/resources/views/layouts/main.blade.php b/flexiapi/resources/views/layouts/main.blade.php index 35f9bda..4542dd6 100644 --- a/flexiapi/resources/views/layouts/main.blade.php +++ b/flexiapi/resources/views/layouts/main.blade.php @@ -12,6 +12,9 @@ + + + @if (config('instance.custom_theme') & file_exists(public_path('css/' . config('app.env') . '.style.css'))) diff --git a/flexiapi/resources/views/parts/graph.blade.php b/flexiapi/resources/views/parts/graph.blade.php new file mode 100644 index 0000000..84d23fd --- /dev/null +++ b/flexiapi/resources/views/parts/graph.blade.php @@ -0,0 +1,27 @@ +
+ + \ No newline at end of file diff --git a/flexiapi/resources/views/parts/sidebar.blade.php b/flexiapi/resources/views/parts/sidebar.blade.php index c6466c1..281e06f 100644 --- a/flexiapi/resources/views/parts/sidebar.blade.php +++ b/flexiapi/resources/views/parts/sidebar.blade.php @@ -7,7 +7,7 @@ if (auth()->user() && auth()->user()->admin) { $items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'people']; $items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'account_box']; - $items['admin.statistics.show.day'] = ['title' => 'Statistics', 'icon' => 'analytics']; + $items['admin.statistics.show'] = ['title' => 'Statistics', 'icon' => 'analytics']; } @endphp diff --git a/flexiapi/resources/views/parts/tabs.blade.php b/flexiapi/resources/views/parts/tabs.blade.php index b502009..29f46f6 100644 --- a/flexiapi/resources/views/parts/tabs.blade.php +++ b/flexiapi/resources/views/parts/tabs.blade.php @@ -1,5 +1,5 @@ diff --git a/flexiapi/resources/views/parts/tabs/register.blade.php b/flexiapi/resources/views/parts/tabs/register.blade.php index be7fd45..21be028 100644 --- a/flexiapi/resources/views/parts/tabs/register.blade.php +++ b/flexiapi/resources/views/parts/tabs/register.blade.php @@ -1,6 +1,6 @@ @if(config('app.phone_authentication')) @include('parts.tabs', ['items' => [ - 'account.register.email' => 'Email registration', - 'account.register.phone' => 'Phone registration', + route('account.register.phone') => 'Phone registration', + route('account.register.email') => 'Email registration', ]]) @endif diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php index b303ddc..ebeabd5 100644 --- a/flexiapi/routes/api.php +++ b/flexiapi/routes/api.php @@ -54,10 +54,6 @@ Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store'); Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt'); Route::group(['middleware' => ['auth.digest_or_key']], function () { - Route::get('statistic/month', 'Api\StatisticController@month'); - Route::get('statistic/week', 'Api\StatisticController@week'); - Route::get('statistic/day', 'Api\StatisticController@day'); - Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach'); Route::get('accounts/me/api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt'); diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 8e7e7e0..eaca5dd 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -32,7 +32,7 @@ use App\Http\Controllers\Admin\AccountTypeController; use App\Http\Controllers\Admin\AccountController as AdminAccountController; use App\Http\Controllers\Admin\ContactsListController; use App\Http\Controllers\Admin\ContactsListContactController; - +use App\Http\Controllers\Admin\StatisticsController; use Illuminate\Support\Facades\Route; Route::redirect('/', '/login')->name('account.home'); @@ -131,11 +131,11 @@ if (config('app.web_panel')) { Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth'); Route::name('admin.')->prefix('admin')->middleware(['auth.admin'])->group(function () { - - // Statistics - Route::get('statistics/day', 'Admin\StatisticsController@showDay')->name('statistics.show.day'); - Route::get('statistics/week', 'Admin\StatisticsController@showWeek')->name('statistics.show.week'); - Route::get('statistics/month', 'Admin\StatisticsController@showMonth')->name('statistics.show.month'); + Route::name('statistics.')->controller(StatisticsController::class)->prefix('statistics')->group(function () { + Route::get('/', 'index')->name('index'); + Route::get('/{type?}', 'show')->name('show'); + Route::post('/', 'edit')->name('edit'); + }); Route::name('account.')->prefix('accounts')->group(function () { Route::controller(AdminAccountController::class)->group(function () {