page de détail, ajout de mesures
This commit is contained in:
parent
bcafef75f1
commit
af1233c246
2 changed files with 523 additions and 313 deletions
|
@ -733,12 +733,64 @@ final class AdminController extends AbstractController
|
||||||
$center = [$first->getLon(), $first->getLat()];
|
$center = [$first->getLon(), $first->getLat()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate current metrics from objects array (from Overpass data)
|
||||||
|
$currentCount = count($objects);
|
||||||
|
|
||||||
|
// Calculate current completion percentage
|
||||||
|
$completionTags = \App\Service\FollowUpService::getFollowUpCompletionTags()[$theme] ?? [];
|
||||||
|
$currentCompletion = 0;
|
||||||
|
|
||||||
|
if ($currentCount > 0 && !empty($completionTags)) {
|
||||||
|
$totalTags = count($completionTags) * $currentCount;
|
||||||
|
$filledTags = 0;
|
||||||
|
|
||||||
|
foreach ($objects as $obj) {
|
||||||
|
// Get the original Place object to check tags
|
||||||
|
$place = null;
|
||||||
|
foreach ($places as $p) {
|
||||||
|
if ($p->getOsmId() === $obj['id'] && $p->getOsmKind() === $obj['osm_kind']) {
|
||||||
|
$place = $p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($place) {
|
||||||
|
foreach ($completionTags as $tag) {
|
||||||
|
// Simple check for name tag
|
||||||
|
if ($tag === 'name' && !empty($place->getName())) {
|
||||||
|
$filledTags++;
|
||||||
|
}
|
||||||
|
// Add more tag checks as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentCompletion = $totalTags > 0 ? round(($filledTags / $totalTags) * 100) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current data to history if empty
|
||||||
|
if (empty($countData)) {
|
||||||
|
$countData[] = [
|
||||||
|
'date' => (new \DateTime())->format('Y-m-d'),
|
||||||
|
'value' => $currentCount
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($completionData)) {
|
||||||
|
$completionData[] = [
|
||||||
|
'date' => (new \DateTime())->format('Y-m-d'),
|
||||||
|
'value' => $currentCompletion
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('admin/followup_theme_graph.html.twig', [
|
return $this->render('admin/followup_theme_graph.html.twig', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
'theme' => $theme,
|
'theme' => $theme,
|
||||||
'theme_label' => $themes[$theme],
|
'theme_label' => $themes[$theme],
|
||||||
'count_data' => json_encode($countData),
|
'count_data' => $countData,
|
||||||
'completion_data' => json_encode($completionData),
|
'completion_data' => $completionData,
|
||||||
|
'current_count' => $currentCount,
|
||||||
|
'current_completion' => $currentCompletion,
|
||||||
'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
||||||
'followup_labels' => $themes,
|
'followup_labels' => $themes,
|
||||||
'geojson' => json_encode($geojson),
|
'geojson' => json_encode($geojson),
|
||||||
|
|
|
@ -96,12 +96,15 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maplibregl-popup-content {
|
.maplibregl-popup-content {
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-josm {
|
.btn-josm {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bouton flottant suggestion desktop */
|
/* Bouton flottant suggestion desktop */
|
||||||
.suggestion-float-btn {
|
.suggestion-float-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -111,13 +114,28 @@
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.suggestion-float-btn { display: block; }
|
.suggestion-float-btn {
|
||||||
.suggestion-footer-btn { display: none !important; }
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.suggestion-footer-btn {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.suggestion-float-btn { display: none !important; }
|
.suggestion-float-btn {
|
||||||
.suggestion-footer-btn { display: block; width: 100%; margin: 0; border-radius: 0; }
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-footer-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -138,9 +156,17 @@
|
||||||
<div class="alert alert-warning" style="font-size:0.95em;">
|
<div class="alert alert-warning" style="font-size:0.95em;">
|
||||||
<b>DEBUG : Objets Place trouvés pour cette ville (avant filtrage)</b><br>
|
<b>DEBUG : Objets Place trouvés pour cette ville (avant filtrage)</b><br>
|
||||||
<table class="table table-sm table-bordered mt-2 mb-0">
|
<table class="table table-sm table-bordered mt-2 mb-0">
|
||||||
<thead><tr>
|
<thead>
|
||||||
<th>#</th><th>id</th><th>main_tag</th><th>osm_kind</th><th>nom</th><th>lat</th><th>lon</th>
|
<tr>
|
||||||
</tr></thead>
|
<th>#</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>main_tag</th>
|
||||||
|
<th>osm_kind</th>
|
||||||
|
<th>nom</th>
|
||||||
|
<th>lat</th>
|
||||||
|
<th>lon</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for p in places %}
|
{% for p in places %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -166,36 +192,13 @@
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mb-0">Code INSEE: {{ stats.zone }}</p>
|
<p class="mb-0">Code INSEE: {{ stats.zone }}</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-outline-secondary">
|
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}"
|
||||||
|
class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-arrow-left"></i> Retour aux stats
|
<i class="bi bi-arrow-left"></i> Retour aux stats
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-bar">
|
|
||||||
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-primary">
|
|
||||||
<i class="bi bi-bar-chart"></i> Stats de la ville
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone}) }}" class="btn btn-warning">
|
|
||||||
<i class="bi bi-arrow-clockwise"></i> Labourer la ville
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('admin_followup_graph', {'insee_code': stats.zone}) }}" class="btn btn-info">
|
|
||||||
<i class="bi bi-graph-up"></i> Suivi OSM (graphes)
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('admin_followup_embed_graph', {'insee_code': stats.zone, 'theme': theme}) }}" class="btn btn-success" target="_blank">
|
|
||||||
<i class="bi bi-box-arrow-up-right"></i> Graphe embedded
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('app_admin') }}" class="btn btn-secondary">
|
|
||||||
<i class="bi bi-house"></i> Accueil admin
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('app_public_stats_evolutions', {'insee_code': stats.zone}) }}" class="btn btn-outline-primary">
|
|
||||||
<i class="bi bi-clock-history"></i> Évolutions temporelles
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('admin_street_completion', {'insee_code': stats.zone}) }}" class="btn btn-outline-success">
|
|
||||||
<i class="bi bi-signpost"></i> Complétion des rues
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if theme == 'bicycle_parking' %}
|
{% if theme == 'bicycle_parking' %}
|
||||||
{% include 'admin/_followup_bicycle_parking_extra.html.twig' %}
|
{% include 'admin/_followup_bicycle_parking_extra.html.twig' %}
|
||||||
|
@ -206,45 +209,53 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if overpass_query is defined %}
|
|
||||||
<a href="https://overpass-turbo.eu/?Q={{ overpass_query|url_encode }}" target="_blank" class="btn btn-outline-primary">
|
|
||||||
<i class="bi bi-geo"></i> Vérifier sur Overpass Turbo
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if josm_url %}
|
{% if josm_url %}
|
||||||
<a href="{{ josm_url }}" class="btn btn-outline-dark btn-josm" target="_blank">
|
<a href="{{ josm_url }}" class="btn btn-outline-dark btn-josm" target="_blank">
|
||||||
<i class="bi bi-box-arrow-up-right"></i> Ouvrir tous les objets dans JOSM
|
<i class="bi bi-box-arrow-up-right"></i> Ouvrir tous les objets dans JOSM
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-info mb-3">Aucun objet sélectionné pour ce thème, rien à charger dans JOSM.</div>
|
<div class="alert alert-info mb-3">Aucun objet sélectionné pour ce thème, rien à charger dans
|
||||||
|
JOSM.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if overpass_query is defined %}
|
||||||
|
<a href="https://overpass-turbo.eu/?Q={{ overpass_query|url_encode }}" target="_blank"
|
||||||
|
class="btn btn-outline-primary">
|
||||||
|
<i class="bi bi-geo"></i> Overpass Turbo
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="themeMap"></div>
|
<div id="themeMap"></div>
|
||||||
|
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value" id="currentCount">-</div>
|
<div class="stat-value" id="currentCount">{{ current_count }}</div>
|
||||||
<div class="stat-label">Nombre actuel</div>
|
<div class="stat-label">Nombre actuel</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value" id="currentCompletion">-</div>
|
<div class="stat-value" id="currentCompletion">{{ current_completion }}</div>
|
||||||
<div class="stat-label">Complétion actuelle</div>
|
<div class="stat-label">Complétion actuelle</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
{# <div class="stat-card"> #}
|
||||||
<div class="stat-value" id="dataPoints">-</div>
|
{# <div class="stat-value" id="dataPoints">{{ count_data|length }}</div> #}
|
||||||
<div class="stat-label">Points de données</div>
|
{# <div class="stat-label">Points de données</div> #}
|
||||||
</div>
|
{# </div> #}
|
||||||
<div class="stat-card">
|
{# <div class="stat-card"> #}
|
||||||
<div class="stat-value" id="lastUpdate">-</div>
|
{# <div class="stat-value" id="lastUpdate">{{ last_update }}</div> #}
|
||||||
<div class="stat-label">Dernière mise à jour</div>
|
{# <div class="stat-label">Dernière mise à jour</div> #}
|
||||||
</div>
|
{# </div> #}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<canvas id="themeChart"></canvas>
|
<canvas id="themeChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
{% if completion_tags is defined %}
|
||||||
|
completion_tags
|
||||||
|
{{ dump(completion_tags[theme]) }}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% if completion_tags is defined and completion_tags[theme] is defined %}
|
{% if completion_tags is defined and completion_tags[theme] is defined %}
|
||||||
<div class="card mt-4">
|
<div class="card mt-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -268,7 +279,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
<div id="tags-stats-block">
|
<div id="tags-stats-block">
|
||||||
<table class="table table-sm table-bordered mb-0" id="tags-stats-table" style="max-width:600px;">
|
<table class="table table-sm table-bordered mb-0" id="tags-stats-table"
|
||||||
|
style="max-width:600px;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Tag</th>
|
<th>Tag</th>
|
||||||
|
@ -276,7 +288,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td colspan="2" class="text-muted">Chargement...</td></tr>
|
<tr>
|
||||||
|
<td colspan="2" class="text-muted">Chargement...</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -290,7 +304,8 @@
|
||||||
{% for t, label in followup_labels %}
|
{% for t, label in followup_labels %}
|
||||||
{% if t != theme %}
|
{% if t != theme %}
|
||||||
<li class="list-inline-item mb-2">
|
<li class="list-inline-item mb-2">
|
||||||
<a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': t}) }}" class="btn btn-outline-secondary">
|
<a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': t}) }}"
|
||||||
|
class="btn btn-outline-secondary">
|
||||||
<i class="bi {{ icons[t]|default('bi-question-circle') }}"></i> {{ label }}
|
<i class="bi {{ icons[t]|default('bi-question-circle') }}"></i> {{ label }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -300,7 +315,8 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11" class="btn btn-info suggestion-footer-btn mt-4 mb-2" target="_blank" rel="noopener">
|
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11"
|
||||||
|
class="btn btn-info suggestion-footer-btn mt-4 mb-2" target="_blank" rel="noopener">
|
||||||
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -389,11 +405,14 @@
|
||||||
}
|
}
|
||||||
// Centrage carte
|
// Centrage carte
|
||||||
let lats = [], lons = [];
|
let lats = [], lons = [];
|
||||||
|
let completions_list = [];
|
||||||
data.elements.forEach(e => {
|
data.elements.forEach(e => {
|
||||||
if (e.lat && e.lon) {
|
if (e.lat && e.lon) {
|
||||||
lats.push(e.lat); lons.push(e.lon);
|
lats.push(e.lat);
|
||||||
|
lons.push(e.lon);
|
||||||
} else if (e.type === 'way' && e.center) {
|
} else if (e.type === 'way' && e.center) {
|
||||||
lats.push(e.center.lat); lons.push(e.center.lon);
|
lats.push(e.center.lat);
|
||||||
|
lons.push(e.center.lon);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (lats.length && lons.length) {
|
if (lats.length && lons.length) {
|
||||||
|
@ -405,9 +424,11 @@
|
||||||
data.elements.forEach(e => {
|
data.elements.forEach(e => {
|
||||||
let lat = null, lon = null;
|
let lat = null, lon = null;
|
||||||
if (e.type === 'node') {
|
if (e.type === 'node') {
|
||||||
lat = e.lat; lon = e.lon;
|
lat = e.lat;
|
||||||
|
lon = e.lon;
|
||||||
} else if (e.center) {
|
} else if (e.center) {
|
||||||
lat = e.center.lat; lon = e.center.lon;
|
lat = e.center.lat;
|
||||||
|
lon = e.center.lon;
|
||||||
}
|
}
|
||||||
if (!lat || !lon) return; // On ignore les ways sans centroïde
|
if (!lat || !lon) return; // On ignore les ways sans centroïde
|
||||||
// Calcul de la complétion
|
// Calcul de la complétion
|
||||||
|
@ -423,18 +444,23 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let completion = completionTags && completionTags.length > 0 ? Math.round(100 * filled / completionTags.length) : null;
|
let completion = completionTags && completionTags.length > 0 ? Math.round(100 * filled / completionTags.length) : null;
|
||||||
|
completions_list.push(completion);
|
||||||
|
|
||||||
// Couleur dégradée du gris au vert intense
|
// Couleur dégradée du gris au vert intense
|
||||||
function lerpColor(a, b, t) {
|
function lerpColor(a, b, t) {
|
||||||
// a et b sont des couleurs hex, t entre 0 et 1
|
// a et b sont des couleurs hex, t entre 0 et 1
|
||||||
const ah = a.replace('#', '');
|
const ah = a.replace('#', '');
|
||||||
const bh = b.replace('#', '');
|
const bh = b.replace('#', '');
|
||||||
const ar = parseInt(ah.substring(0,2), 16), ag = parseInt(ah.substring(2,4), 16), ab = parseInt(ah.substring(4,6), 16);
|
const ar = parseInt(ah.substring(0, 2), 16), ag = parseInt(ah.substring(2, 4), 16),
|
||||||
const br = parseInt(bh.substring(0,2), 16), bg = parseInt(bh.substring(2,4), 16), bb = parseInt(bh.substring(4,6), 16);
|
ab = parseInt(ah.substring(4, 6), 16);
|
||||||
|
const br = parseInt(bh.substring(0, 2), 16), bg = parseInt(bh.substring(2, 4), 16),
|
||||||
|
bb = parseInt(bh.substring(4, 6), 16);
|
||||||
const rr = Math.round(ar + (br - ar) * t);
|
const rr = Math.round(ar + (br - ar) * t);
|
||||||
const rg = Math.round(ag + (bg - ag) * t);
|
const rg = Math.round(ag + (bg - ag) * t);
|
||||||
const rb = Math.round(ab + (bb - ab) * t);
|
const rb = Math.round(ab + (bb - ab) * t);
|
||||||
return '#' + rr.toString(16).padStart(2, '0') + rg.toString(16).padStart(2, '0') + rb.toString(16).padStart(2, '0');
|
return '#' + rr.toString(16).padStart(2, '0') + rg.toString(16).padStart(2, '0') + rb.toString(16).padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = '#cccccc'; // gris par défaut
|
let color = '#cccccc'; // gris par défaut
|
||||||
if (completion !== null) {
|
if (completion !== null) {
|
||||||
color = lerpColor('#cccccc', '#008000', Math.max(0, Math.min(1, completion / 100)));
|
color = lerpColor('#cccccc', '#008000', Math.max(0, Math.min(1, completion / 100)));
|
||||||
|
@ -479,6 +505,20 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const average_completion = completions_list.reduce((a, b) => a + b, 0) / completions_list.length;
|
||||||
|
|
||||||
|
const count_objects = data.elements.length;
|
||||||
|
const current_count = document.querySelector('#currentCount');
|
||||||
|
const current_completion = document.querySelector('#currentCompletion');
|
||||||
|
|
||||||
|
if (current_count && count_objects) {
|
||||||
|
current_count.textContent = count_objects;
|
||||||
|
}
|
||||||
|
if (average_completion && current_completion) {
|
||||||
|
current_completion.textContent = average_completion.toFixed(2) + ' %';
|
||||||
|
}
|
||||||
const tbody = document.querySelector('#tags-stats-table tbody');
|
const tbody = document.querySelector('#tags-stats-table tbody');
|
||||||
if (Object.keys(tagCounts).length === 0) {
|
if (Object.keys(tagCounts).length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="2" class="text-muted">Aucun tag trouvé</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="2" class="text-muted">Aucun tag trouvé</td></tr>';
|
||||||
|
@ -501,24 +541,40 @@
|
||||||
|
|
||||||
|
|
||||||
const countData = {{ count_data|json_encode|raw }};
|
const countData = {{ count_data|json_encode|raw }};
|
||||||
console.log(countData)
|
console.log('Count data:', countData);
|
||||||
|
|
||||||
|
|
||||||
const completionData = {{ completion_data|json_encode|raw }};
|
const completionData = {{ completion_data|json_encode|raw }};
|
||||||
|
console.log('Completion data:', completionData);
|
||||||
|
|
||||||
|
// Current metrics from server
|
||||||
|
const currentCount = {{ current_count }};
|
||||||
|
const currentCompletion = {{ current_completion }};
|
||||||
|
|
||||||
// Mettre à jour les statistiques
|
// Mettre à jour les statistiques
|
||||||
function updateStats() {
|
function updateStats() {
|
||||||
if (Array.isArray(countData) && countData.length > 0) {
|
// Use current metrics from server if available
|
||||||
|
if (typeof currentCount !== 'undefined') {
|
||||||
|
document.getElementById('currentCount').textContent = currentCount;
|
||||||
|
} else if (Array.isArray(countData) && countData.length > 0) {
|
||||||
const latestCount = countData[countData.length - 1];
|
const latestCount = countData[countData.length - 1];
|
||||||
document.getElementById('currentCount').textContent = latestCount.value;
|
document.getElementById('currentCount').textContent = latestCount.value;
|
||||||
document.getElementById('lastUpdate').textContent = new Date(latestCount.date).toLocaleDateString('fr-FR');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(completionData) && completionData.length > 0) {
|
if (typeof currentCompletion !== 'undefined') {
|
||||||
|
document.getElementById('currentCompletion').textContent = currentCompletion + '%';
|
||||||
|
} else if (Array.isArray(completionData) && completionData.length > 0) {
|
||||||
const latestCompletion = completionData[completionData.length - 1];
|
const latestCompletion = completionData[completionData.length - 1];
|
||||||
document.getElementById('currentCompletion').textContent = latestCompletion.value + '%';
|
document.getElementById('currentCompletion').textContent = latestCompletion.value + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set last update date
|
||||||
|
if (Array.isArray(countData) && countData.length > 0) {
|
||||||
|
const latestCount = countData[countData.length - 1];
|
||||||
|
document.getElementById('lastUpdate').textContent = new Date(latestCount.date).toLocaleDateString('fr-FR');
|
||||||
|
} else {
|
||||||
|
document.getElementById('lastUpdate').textContent = new Date().toLocaleDateString('fr-FR');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('dataPoints').textContent = Math.max(
|
document.getElementById('dataPoints').textContent = Math.max(
|
||||||
Array.isArray(countData) ? countData.length : 0,
|
Array.isArray(countData) ? countData.length : 0,
|
||||||
Array.isArray(completionData) ? completionData.length : 0
|
Array.isArray(completionData) ? completionData.length : 0
|
||||||
|
@ -581,50 +637,152 @@
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.1,
|
tension: 0.1,
|
||||||
yAxisID: 'y1',
|
yAxisID: 'y1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Pourcentage de complétion',
|
label: 'Pourcentage de complétion',
|
||||||
data: Array.isArray(completionData) ? completionData.map(d => ({ x: new Date(d.date), y: d.value })) : [],
|
data
|
||||||
borderColor: '#198754',
|
:
|
||||||
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
Array.isArray(completionData) ? completionData.map(d => ({x: new Date(d.date), y: d.value})) : [],
|
||||||
borderWidth: 2,
|
borderColor
|
||||||
fill: true,
|
:
|
||||||
tension: 0.1,
|
'#198754',
|
||||||
yAxisID: 'y2',
|
backgroundColor
|
||||||
|
:
|
||||||
|
'rgba(25, 135, 84, 0.1)',
|
||||||
|
borderWidth
|
||||||
|
:
|
||||||
|
2,
|
||||||
|
fill
|
||||||
|
:
|
||||||
|
true,
|
||||||
|
tension
|
||||||
|
:
|
||||||
|
0.1,
|
||||||
|
yAxisID
|
||||||
|
:
|
||||||
|
'y2',
|
||||||
}
|
}
|
||||||
|
,
|
||||||
|
// Add current data point if no historical data exists
|
||||||
|
...
|
||||||
|
((!Array.isArray(countData) || countData.length === 0) && typeof currentCount !== 'undefined' ? [{
|
||||||
|
label: "Nombre actuel",
|
||||||
|
data: [{x: new Date(), y: currentCount}],
|
||||||
|
borderColor: '#dc3545',
|
||||||
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 5,
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y1',
|
||||||
|
}] : []),
|
||||||
|
...
|
||||||
|
((!Array.isArray(completionData) || completionData.length === 0) && typeof currentCompletion !== 'undefined' ? [{
|
||||||
|
label: "Complétion actuelle",
|
||||||
|
data: [{x: new Date(), y: currentCompletion}],
|
||||||
|
borderColor: '#fd7e14',
|
||||||
|
backgroundColor: 'rgba(253, 126, 20, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 5,
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y2',
|
||||||
|
}] : [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio
|
||||||
plugins: {
|
:
|
||||||
legend: { display: true },
|
false,
|
||||||
tooltip: { mode: 'index', intersect: false }
|
plugins
|
||||||
},
|
:
|
||||||
interaction: { mode: 'nearest', axis: 'x', intersect: false },
|
{
|
||||||
|
legend: {
|
||||||
|
display: true
|
||||||
|
}
|
||||||
|
,
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index', intersect
|
||||||
|
:
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
|
interaction: {
|
||||||
|
mode: 'nearest', axis
|
||||||
|
:
|
||||||
|
'x', intersect
|
||||||
|
:
|
||||||
|
false
|
||||||
|
}
|
||||||
|
,
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
time: { unit: 'day', displayFormats: { day: 'dd/MM/yyyy' } },
|
time
|
||||||
title: { display: true, text: 'Date' }
|
:
|
||||||
},
|
{
|
||||||
|
unit: 'day', displayFormats
|
||||||
|
:
|
||||||
|
{
|
||||||
|
day: 'dd/MM/yyyy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
|
title: {
|
||||||
|
display: true, text
|
||||||
|
:
|
||||||
|
'Date'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
y1: {
|
y1: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
position: 'left',
|
position
|
||||||
title: { display: true, text: "Nombre d'objets" },
|
:
|
||||||
|
'left',
|
||||||
|
title
|
||||||
|
:
|
||||||
|
{
|
||||||
|
display: true, text
|
||||||
|
:
|
||||||
|
"Nombre d'objets"
|
||||||
|
}
|
||||||
|
,
|
||||||
beginAtZero: true
|
beginAtZero: true
|
||||||
},
|
}
|
||||||
|
,
|
||||||
y2: {
|
y2: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
position: 'right',
|
position
|
||||||
title: { display: true, text: 'Complétion (%)' },
|
:
|
||||||
|
'right',
|
||||||
|
title
|
||||||
|
:
|
||||||
|
{
|
||||||
|
display: true, text
|
||||||
|
:
|
||||||
|
'Complétion (%)'
|
||||||
|
}
|
||||||
|
,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max
|
||||||
grid: { drawOnChartArea: false }
|
:
|
||||||
|
100,
|
||||||
|
grid
|
||||||
|
:
|
||||||
|
{
|
||||||
|
drawOnChartArea: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
// Initialiser les statistiques
|
// Initialiser les statistiques
|
||||||
updateStats();
|
updateStats();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue