commit
3f15cb99fd
11 changed files with 300 additions and 49 deletions
20
app/assets/javascripts/toggle_chart.js
Normal file
20
app/assets/javascripts/toggle_chart.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
var TPS = TPS || {};
|
||||||
|
|
||||||
|
TPS.toggleChart = function(event, chartClass) {
|
||||||
|
var nextSelectorItem = $(event.target),
|
||||||
|
nextChart = $(chartClass),
|
||||||
|
nextChartId = nextChart.children().first().attr("id"),
|
||||||
|
currentSelectorItem = nextSelectorItem.parent().find(".segmented-control-item-active"),
|
||||||
|
currentChart = nextSelectorItem.parent().parent().find(".chart:not(.hidden)");
|
||||||
|
|
||||||
|
// Change the current selector and the next selector states
|
||||||
|
currentSelectorItem.toggleClass("segmented-control-item-active");
|
||||||
|
nextSelectorItem.toggleClass("segmented-control-item-active");
|
||||||
|
|
||||||
|
// Hide the currently shown chart and show the new one
|
||||||
|
currentChart.toggleClass("hidden");
|
||||||
|
nextChart.toggleClass("hidden");
|
||||||
|
|
||||||
|
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
|
||||||
|
Chartkick.charts[nextChartId].getChartObject().reflow();
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
@import "card";
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
.stat-card {
|
|
||||||
@extend .card;
|
|
||||||
margin: 15px auto;
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
|
||||||
}
|
|
124
app/assets/stylesheets/stats.scss
Normal file
124
app/assets/stylesheets/stats.scss
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
@import "card";
|
||||||
|
|
||||||
|
$dark-grey: #333333;
|
||||||
|
$light-grey: #999999;
|
||||||
|
$blue: rgba(61, 149, 236, 1);
|
||||||
|
$blue-hover: rgba(61, 149, 236, 0.8);
|
||||||
|
|
||||||
|
$default-space: 15px;
|
||||||
|
|
||||||
|
$new-h1-margin-bottom: 4 * $default-space;
|
||||||
|
.new-h1 {
|
||||||
|
color: $dark-grey;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: $new-h1-margin-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statistiques-padding-top: $default-space * 2;
|
||||||
|
.statistiques {
|
||||||
|
width: 1040px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: $statistiques-padding-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-cards {
|
||||||
|
.stat-card:nth-of-type(even) {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stat-card-margin-bottom: 3 * $default-space;
|
||||||
|
.stat-card {
|
||||||
|
@extend .card;
|
||||||
|
margin-bottom: $stat-card-margin-bottom;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stat-card-half-horizontal-spacing: 4 * $default-space;
|
||||||
|
.stat-card-half {
|
||||||
|
width: calc((100% - #{$stat-card-half-horizontal-spacing}) / 2);
|
||||||
|
margin-right: 3 * $default-space;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-title {
|
||||||
|
color: $dark-grey;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
$segmented-control-margin-top: $default-space;
|
||||||
|
.segmented-control {
|
||||||
|
border-radius: 36px;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-size: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: $segmented-control-margin-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
$segmented-control-item-horizontal-padding: $default-space;
|
||||||
|
$segmented-control-item-border-radius: 2 * $default-space;
|
||||||
|
.segmented-control-item {
|
||||||
|
color: $blue;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 15px;
|
||||||
|
border: 2px solid $blue;
|
||||||
|
margin-right: -2px;
|
||||||
|
padding-left: $segmented-control-item-horizontal-padding;
|
||||||
|
padding-right: $segmented-control-item-horizontal-padding;
|
||||||
|
color: $blue;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
border-radius: $segmented-control-item-border-radius 0px 0px $segmented-control-item-border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-radius: 0px $segmented-control-item-border-radius $segmented-control-item-border-radius 0px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $blue-hover;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented-control-item-active {
|
||||||
|
background-color: $blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
margin-top: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
$big-number-card-padding: 2 * $segmented-control-item-border-radius;
|
||||||
|
.big-number-card {
|
||||||
|
padding: $big-number-card-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-number-card-title {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
color: $light-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-number-card-number {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 90px;
|
||||||
|
line-height: 90px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $blue;
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
module Administrations
|
|
||||||
class StatsController < ApplicationController
|
|
||||||
before_action :authenticate_administration!
|
|
||||||
|
|
||||||
def index
|
|
||||||
procedures = Procedure.where(created_at: Time.current.all_quarter).group("date_trunc('day', created_at)").count
|
|
||||||
dossiers = Dossier.where(created_at: Time.current.all_quarter).group("date_trunc('day', created_at)").count
|
|
||||||
@procedures = clean_hash(procedures)
|
|
||||||
@dossiers = clean_hash(dossiers)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def clean_hash h
|
|
||||||
h.keys.each{ |key| h[key.to_date] = h[key]; h.delete(key) }
|
|
||||||
min_date = h.keys.min
|
|
||||||
max_date = h.keys.max
|
|
||||||
(min_date..max_date).each do |date|
|
|
||||||
h[date] = 0 if h[date].nil?
|
|
||||||
end
|
|
||||||
h
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
56
app/controllers/stats_controller.rb
Normal file
56
app/controllers/stats_controller.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
class StatsController < ApplicationController
|
||||||
|
|
||||||
|
def index
|
||||||
|
procedures = Procedure.where(:published => true)
|
||||||
|
dossiers = Dossier.where.not(:state => :draft)
|
||||||
|
|
||||||
|
@procedures_30_days_flow = thirty_days_flow_hash(procedures)
|
||||||
|
@dossiers_30_days_flow = thirty_days_flow_hash(dossiers)
|
||||||
|
|
||||||
|
@procedures_cumulative = cumulative_hash(procedures)
|
||||||
|
@dossiers_cumulative = cumulative_hash(dossiers)
|
||||||
|
|
||||||
|
@procedures_count = procedures.count
|
||||||
|
@dossiers_count = dossiers.count
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def thirty_days_flow_hash(association)
|
||||||
|
min_date = 30.days.ago.to_date
|
||||||
|
max_date = Time.now.to_date
|
||||||
|
|
||||||
|
thirty_days_flow_hash = association
|
||||||
|
.where(:created_at => min_date..max_date)
|
||||||
|
.group("date_trunc('day', created_at)")
|
||||||
|
.count
|
||||||
|
|
||||||
|
clean_hash(thirty_days_flow_hash, min_date, max_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_hash(h, min_date, max_date)
|
||||||
|
# Convert keys to date
|
||||||
|
h = Hash[h.map { |(k, v)| [k.to_date, v] }]
|
||||||
|
|
||||||
|
# Add missing vales where count is 0
|
||||||
|
(min_date..max_date).each do |date|
|
||||||
|
if h[date].nil?
|
||||||
|
h[date] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
|
def cumulative_hash(association)
|
||||||
|
sum = 0
|
||||||
|
association
|
||||||
|
.group("DATE_TRUNC('month', created_at)")
|
||||||
|
.count
|
||||||
|
.to_a
|
||||||
|
.sort{ |x, y| x[0] <=> y[0] }
|
||||||
|
.map { |x, y| { x => (sum += y)} }
|
||||||
|
.reduce({}, :merge)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -7,9 +7,6 @@
|
||||||
|
|
||||||
= f.submit 'Créer un administrateur', class: 'btn btn-success', id: 'submit_new_administrateur'
|
= f.submit 'Créer un administrateur', class: 'btn btn-success', id: 'submit_new_administrateur'
|
||||||
|
|
||||||
.text-center
|
|
||||||
= link_to 'Stats', administrations_stats_path, style: 'margin-bottom: 50px; display: block;', 'data-no-turbolink': true
|
|
||||||
|
|
||||||
= smart_listing_render :admins
|
= smart_listing_render :admins
|
||||||
|
|
||||||
%br
|
%br
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
= javascript_include_tag 'https://code.highcharts.com/highcharts.js', 'chartkick'
|
|
||||||
|
|
||||||
.container
|
|
||||||
.stats
|
|
||||||
.stat-card
|
|
||||||
%h1 Procédures créées
|
|
||||||
= line_chart @procedures
|
|
||||||
|
|
||||||
.stat-card
|
|
||||||
%h1 Dossiers créés
|
|
||||||
= line_chart @dossiers
|
|
|
@ -5,6 +5,8 @@
|
||||||
\-
|
\-
|
||||||
= link_to 'Nouveautés', 'https://github.com/sgmap/tps/releases', target: '_blank'
|
= link_to 'Nouveautés', 'https://github.com/sgmap/tps/releases', target: '_blank'
|
||||||
\-
|
\-
|
||||||
|
= link_to 'Statistiques', stats_path
|
||||||
|
\-
|
||||||
= link_to 'CGU / Mentions légales', cgu_path
|
= link_to 'CGU / Mentions légales', cgu_path
|
||||||
\-
|
\-
|
||||||
= link_to 'Contact', "mailto:"+t('dynamics.contact_email')
|
= link_to 'Contact', "mailto:"+t('dynamics.contact_email')
|
||||||
|
|
53
app/views/stats/index.html.haml
Normal file
53
app/views/stats/index.html.haml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
= javascript_include_tag 'https://code.highcharts.com/highcharts.js', 'chartkick'
|
||||||
|
|
||||||
|
.statistiques
|
||||||
|
|
||||||
|
%h1.new-h1 Statistiques
|
||||||
|
|
||||||
|
.stat-cards
|
||||||
|
|
||||||
|
.stat-card.stat-card-half.pull-left
|
||||||
|
%ul.segmented-control.pull-right
|
||||||
|
%li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.cumulative-procedures-chart');" }
|
||||||
|
Cumul
|
||||||
|
%li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.flux-procedures-chart');" }
|
||||||
|
Flux (30 jours)
|
||||||
|
%span.stat-card-title.pull-left Démarches dématérialisées
|
||||||
|
.clearfix
|
||||||
|
|
||||||
|
.chart-container
|
||||||
|
.chart.cumulative-procedures-chart
|
||||||
|
= area_chart @procedures_cumulative,
|
||||||
|
:colors => ["rgba(61, 149, 236, 1)"]
|
||||||
|
.chart.flux-procedures-chart.hidden
|
||||||
|
= line_chart @procedures_30_days_flow,
|
||||||
|
:colors => ["rgba(61, 149, 236, 1)"]
|
||||||
|
|
||||||
|
.stat-card.stat-card-half.pull-left
|
||||||
|
%ul.segmented-control.pull-right
|
||||||
|
%li.segmented-control-item.segmented-control-item-active{ :onclick => "TPS.toggleChart(event, '.cumulative-dossiers-chart');" }
|
||||||
|
Cumul
|
||||||
|
%li.segmented-control-item{ :onclick => "TPS.toggleChart(event, '.flux-dossiers-chart');" }
|
||||||
|
Flux (30 jours)
|
||||||
|
%span.stat-card-title.pull-left Dossiers déposés
|
||||||
|
.clearfix
|
||||||
|
|
||||||
|
.chart-container
|
||||||
|
.chart.cumulative-dossiers-chart
|
||||||
|
= area_chart @dossiers_cumulative,
|
||||||
|
:colors => ["rgba(61, 149, 236, 1)"]
|
||||||
|
.chart.flux-dossiers-chart.hidden
|
||||||
|
= line_chart @dossiers_30_days_flow,
|
||||||
|
:colors => ["rgba(61, 149, 236, 1)"]
|
||||||
|
|
||||||
|
.stat-card.stat-card-half.big-number-card.pull-left
|
||||||
|
%span.big-number-card-title TOTAL DÉMARCHES DÉMATÉRIALISÉES
|
||||||
|
%span.big-number-card-number
|
||||||
|
= @procedures_count
|
||||||
|
|
||||||
|
.stat-card.stat-card-half.big-number-card.pull-left
|
||||||
|
%span.big-number-card-title TOTAL DOSSIERS DÉPOSÉS
|
||||||
|
%span.big-number-card-number
|
||||||
|
= @dossiers_count
|
||||||
|
|
||||||
|
.clearfix
|
|
@ -45,14 +45,14 @@ Rails.application.routes.draw do
|
||||||
authenticate :administration do
|
authenticate :administration do
|
||||||
resources :administrations, only: [:index, :create]
|
resources :administrations, only: [:index, :create]
|
||||||
namespace :administrations do
|
namespace :administrations do
|
||||||
resources :stats, only: [:index]
|
|
||||||
|
|
||||||
require 'sidekiq/web'
|
require 'sidekiq/web'
|
||||||
require 'sidekiq/cron/web'
|
require 'sidekiq/cron/web'
|
||||||
mount Sidekiq::Web => '/sidekiq'
|
mount Sidekiq::Web => '/sidekiq'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :stats, only: [:index]
|
||||||
|
|
||||||
namespace :france_connect do
|
namespace :france_connect do
|
||||||
get 'particulier' => 'particulier#login'
|
get 'particulier' => 'particulier#login'
|
||||||
get 'particulier/callback' => 'particulier#callback'
|
get 'particulier/callback' => 'particulier#callback'
|
||||||
|
|
43
spec/controllers/stats_controller_spec.rb
Normal file
43
spec/controllers/stats_controller_spec.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe StatsController, type: :controller do
|
||||||
|
describe '#thirty_days_flow_hash' do
|
||||||
|
before do
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 45.days.ago)
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 15.days.ago)
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 1.day.ago)
|
||||||
|
|
||||||
|
@expected_hash = {}
|
||||||
|
(30.days.ago.to_date..Time.now.to_date).each do |day|
|
||||||
|
if [15.days.ago.to_date, 1.day.ago.to_date].include?(day)
|
||||||
|
@expected_hash[day] = 1
|
||||||
|
else
|
||||||
|
@expected_hash[day] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let (:association) { Procedure.all }
|
||||||
|
|
||||||
|
subject { StatsController.new.send(:thirty_days_flow_hash, association) }
|
||||||
|
|
||||||
|
it { expect(subject).to eq(@expected_hash) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#cumulative_hash' do
|
||||||
|
before do
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 45.days.ago)
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 15.days.ago)
|
||||||
|
FactoryGirl.create(:procedure, :created_at => 15.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
let (:association) { Procedure.all }
|
||||||
|
|
||||||
|
subject { StatsController.new.send(:cumulative_hash, association) }
|
||||||
|
|
||||||
|
it { expect(subject).to eq({
|
||||||
|
45.days.ago.beginning_of_month => 1,
|
||||||
|
15.days.ago.beginning_of_month => 3
|
||||||
|
}) }
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue