Browse Source

Improve stats page layout (#7243)

* New stats box layout

* repartition: table overview improved with links and icons

* Show selected feed name in title

* i18n string: overview

* fix

* fix

* fix

* delete unused stat in frss

* Update app/i18n/fr/admin.php

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
maTh 1 year ago
parent
commit
afb6f788fa

+ 2 - 1
app/i18n/cs/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Všechny kanály',
 		'no_idle' => 'Nejsou žádné nečinné kanály!',
 		'number_entries' => '%d článků',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% ze všech',
-		'repartition' => 'Přerozdělení článků',
+		'repartition' => 'Přerozdělení článků: %s',
 		'status_favorites' => 'Oblíbené',
 		'status_read' => 'Přečtené',
 		'status_total' => 'Celkem',

+ 2 - 1
app/i18n/de/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Haupt-Feeds',
 		'no_idle' => 'Es gibt keinen inaktiven Feed!',
 		'number_entries' => '%d Artikel',
+		'overview' => 'Übersicht',
 		'percent_of_total' => '% Gesamt',
-		'repartition' => 'Artikel-Verteilung',
+		'repartition' => 'Artikel-Verteilung: %s',
 		'status_favorites' => 'Favoriten',
 		'status_read' => 'Gelesen',
 		'status_total' => 'Gesamt',

+ 2 - 1
app/i18n/el/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Κύρια ροή',
 		'no_idle' => 'Δεν υπάρχουν αδρανείς τροφοδοσίες!',
 		'number_entries' => '%d άρθρα',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% εκ του συνόλου',
-		'repartition' => 'Articles repartition',	// TODO
+		'repartition' => 'Articles repartition: %s',	// TODO
 		'status_favorites' => 'Αγαπημένα',
 		'status_read' => 'Ανάγνωση',
 		'status_total' => 'Σύνολο',

+ 2 - 1
app/i18n/en-us/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Main stream',	// IGNORE
 		'no_idle' => 'There are no idle feeds!',	// IGNORE
 		'number_entries' => '%d articles',	// IGNORE
+		'overview' => 'Overview',	// IGNORE
 		'percent_of_total' => '% of total',	// IGNORE
-		'repartition' => 'Articles repartition',	// IGNORE
+		'repartition' => 'Articles repartition: %s',	// IGNORE
 		'status_favorites' => 'Favorites',
 		'status_read' => 'Read',	// IGNORE
 		'status_total' => 'Total',	// IGNORE

+ 2 - 1
app/i18n/en/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Main stream',
 		'no_idle' => 'There are no idle feeds!',
 		'number_entries' => '%d articles',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% of total',
-		'repartition' => 'Articles repartition',
+		'repartition' => 'Articles repartition: %s',
 		'status_favorites' => 'Favourites',
 		'status_read' => 'Read',
 		'status_total' => 'Total',

+ 2 - 1
app/i18n/es/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Salida principal',
 		'no_idle' => 'No hay fuentes inactivas',
 		'number_entries' => '%d artículos',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% del total',
-		'repartition' => 'Reparto de artículos',
+		'repartition' => 'Reparto de artículos: %s',
 		'status_favorites' => 'Favoritos',
 		'status_read' => 'Leídos',
 		'status_total' => 'Total',	// IGNORE

+ 2 - 1
app/i18n/fa/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => ' جریان اصلی',
 		'no_idle' => ' هیچ فید بیکار وجود ندارد!',
 		'number_entries' => ' %d مقاله',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => ' درصد از کل',
-		'repartition' => ' تقسیم مجدد مقالات',
+		'repartition' => ' تقسیم مجدد مقالات: %s',	// DIRTY
 		'status_favorites' => ' موارد دلخواه',
 		'status_read' => ' بخوانید',
 		'status_total' => ' مجموع',

+ 2 - 1
app/i18n/fi/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Pääsyötevirta',
 		'no_idle' => 'Hiljentyneitä syötteitä ei ole.',
 		'number_entries' => '%d artikkelia',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% kaikista',
-		'repartition' => 'Artikkelien uudelleenjaottelu',
+		'repartition' => 'Artikkelien uudelleenjaottelu: %s',
 		'status_favorites' => 'Suosikit',
 		'status_read' => 'Luetut',
 		'status_total' => 'Yhteensä',

+ 2 - 1
app/i18n/fr/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Flux principal',
 		'no_idle' => 'Il n’y a aucun flux inactif !',
 		'number_entries' => '%d articles',	// IGNORE
+		'overview' => 'Vue d’ensemble',
 		'percent_of_total' => '% du total',
-		'repartition' => 'Répartition des articles',
+		'repartition' => 'Répartition des articles: %s',
 		'status_favorites' => 'favoris',
 		'status_read' => 'lus',
 		'status_total' => 'total',

+ 2 - 1
app/i18n/he/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'הזנה ראשית',
 		'no_idle' => 'אין הזנות מובטלות!',
 		'number_entries' => '%d מאמרים',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% מסך הכל',
-		'repartition' => 'חלוקת המאמרים',
+		'repartition' => 'חלוקת המאמרים: %s',	// DIRTY
 		'status_favorites' => 'מועדפים',
 		'status_read' => 'נקרא',
 		'status_total' => 'סך הכל',

+ 2 - 1
app/i18n/hu/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Minden cikk',
 		'no_idle' => 'Nincsenek tétlen hírforrások!',
 		'number_entries' => '%d cikk',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% az összesből',
-		'repartition' => 'Cikkek eloszlása',
+		'repartition' => 'Cikkek eloszlása: %s',
 		'status_favorites' => 'Kedvencek',
 		'status_read' => 'Olvasott',
 		'status_total' => 'Összes',

+ 2 - 1
app/i18n/id/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Aliran utama',
 		'no_idle' => 'Tidak ada idle feed!',
 		'number_entries' => '%d artikel',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% dari total',
-		'repartition' => 'Mengembalikan artikel',
+		'repartition' => 'Mengembalikan artikel: %s',
 		'status_favorites' => 'Favorites',
 		'status_read' => 'Terbaca',
 		'status_total' => 'Total',	// TODO

+ 2 - 1
app/i18n/it/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Flusso principale',
 		'no_idle' => 'Non ci sono feed non aggiornati',
 		'number_entries' => '%d articoli',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% del totale',
-		'repartition' => 'Ripartizione articoli',
+		'repartition' => 'Ripartizione articoli: %s',
 		'status_favorites' => 'Preferiti',
 		'status_read' => 'Letti',
 		'status_total' => 'Totale',

+ 2 - 1
app/i18n/ja/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => '主なストリーム',
 		'no_idle' => '未使用のフィードはありません!',
 		'number_entries' => '%d 記事',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% 総計',
-		'repartition' => '記事の仕切り',
+		'repartition' => '記事の仕切り: %s',	// DIRTY
 		'status_favorites' => 'お気に入り',
 		'status_read' => '既読',
 		'status_total' => 'すべて',

+ 2 - 1
app/i18n/ko/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => '메인 스트림',
 		'no_idle' => '유휴 피드가 없습니다!',
 		'number_entries' => '%d 개의 글',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '전체에서의 비율 (%)',
-		'repartition' => '글 분류',
+		'repartition' => '글 분류: %s',	// DIRTY
 		'status_favorites' => '즐겨찾기',
 		'status_read' => '읽음',
 		'status_total' => '전체',

+ 2 - 1
app/i18n/lv/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Galvenā plūsma',
 		'no_idle' => 'Nav neaktīvu barotņu!',
 		'number_entries' => '%d raksti',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% no kopsummas',
-		'repartition' => 'Rakstu pārdalīšana',
+		'repartition' => 'Rakstu pārdalīšana: %s',	// DIRTY
 		'status_favorites' => 'Mīļākie',
 		'status_read' => 'Izlasīti',
 		'status_total' => 'Kopā',

+ 2 - 1
app/i18n/nl/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Overzicht',
 		'no_idle' => 'Er is geen gepauzeerde feed!',
 		'number_entries' => '%d artikelen',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% van totaal',
-		'repartition' => 'Artikelverdeling',
+		'repartition' => 'Artikelverdeling: %s',
 		'status_favorites' => 'Favorieten',
 		'status_read' => 'Gelezen',
 		'status_total' => 'Totaal',

+ 2 - 1
app/i18n/oc/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Flux màger',
 		'no_idle' => 'I a pas cap d’article inactiu !',
 		'number_entries' => '%d articles',	// IGNORE
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% del total',
-		'repartition' => 'Reparticion dels articles',
+		'repartition' => 'Reparticion dels articles: %s',
 		'status_favorites' => 'Favorits',
 		'status_read' => 'Legit',
 		'status_total' => 'Total',	// IGNORE

+ 2 - 1
app/i18n/pl/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Kanał główny',
 		'no_idle' => 'Brak bezczynnych kanałów!',
 		'number_entries' => '%d wiadomości',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% wszystkich',
-		'repartition' => 'Podział wiadomości',
+		'repartition' => 'Podział wiadomości: %s',
 		'status_favorites' => 'Ulubione',
 		'status_read' => 'Przeczytane',
 		'status_total' => 'Wszystkie',

+ 2 - 1
app/i18n/pt-br/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Stream principal',
 		'no_idle' => 'Não há nenhum feed inativo!',
 		'number_entries' => '%d artigos',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% do total',
-		'repartition' => 'Repartição de artigos',
+		'repartition' => 'Repartição de artigos: %s',
 		'status_favorites' => 'Favoritos',
 		'status_read' => 'Lido',
 		'status_total' => 'Total',	// IGNORE

+ 2 - 1
app/i18n/ru/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Основной поток',
 		'no_idle' => 'Нет неактивных лент!',
 		'number_entries' => 'статей: %d',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% от всего',
-		'repartition' => 'Распределение статей',
+		'repartition' => 'Распределение статей: %s',
 		'status_favorites' => 'В избранном',
 		'status_read' => 'Прочитано',
 		'status_total' => 'Всего',

+ 2 - 1
app/i18n/sk/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Všetky kanály',
 		'no_idle' => 'Žiadne neaktívne kanály!',
 		'number_entries' => 'Počet článkov: %d',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => 'Z celkového počtu: %',
-		'repartition' => 'Rozdelenie článkov',
+		'repartition' => 'Rozdelenie článkov: %s',
 		'status_favorites' => 'Obľúbené',
 		'status_read' => 'Prečítané',
 		'status_total' => 'Spolu',

+ 2 - 1
app/i18n/tr/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => 'Ana akış',
 		'no_idle' => 'Boşta akış yok!',
 		'number_entries' => '%d makale',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '% toplamın yüzdesi',
-		'repartition' => 'Makale dağılımı',
+		'repartition' => 'Makale dağılımı: %s',
 		'status_favorites' => 'Favoriler',
 		'status_read' => 'Okunmuş',
 		'status_total' => 'Toplam',

+ 2 - 1
app/i18n/zh-cn/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => '首页',
 		'no_idle' => '订阅源近期皆有更新!',
 		'number_entries' => '%d 篇文章',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '%',
-		'repartition' => '文章分布',
+		'repartition' => '文章分布: %s',	// DIRTY
 		'status_favorites' => '收藏',
 		'status_read' => '已读',
 		'status_total' => '总计',

+ 2 - 1
app/i18n/zh-tw/admin.php

@@ -148,8 +148,9 @@ return array(
 		'main_stream' => '首頁',
 		'no_idle' => '訂閱源近期皆有更新!',
 		'number_entries' => '%d 篇文章',
+		'overview' => 'Overview',	// TODO
 		'percent_of_total' => '%',
-		'repartition' => '文章分布',
+		'repartition' => '文章分布: %s',	// DIRTY
 		'status_favorites' => '收藏',
 		'status_read' => '已讀',
 		'status_total' => '總計',

+ 6 - 4
app/layout/aside_subscription.phtml

@@ -52,7 +52,9 @@
 		</li>
 	</ul>
 </nav>
-<a class="close-aside" href="#close">❌</a>
-<nav class="nav_menu nav_mobile">
-	<a class="btn toggle_aside" href="#aside_feed"><?= _i('category') ?></a>
-</nav>
+<?php if (Minz_Request::actionName() != 'repartition') { ?>
+	<a class="close-aside" href="#close">❌</a>
+	<nav class="nav_menu nav_mobile">
+		<a class="btn toggle_aside" href="#aside_feed"><?= _i('category') ?></a>
+	</nav>
+<?php } ?>

+ 73 - 73
app/views/stats/index.phtml

@@ -6,77 +6,77 @@
 <main class="post">
 	<h1><?= _t('admin.stats.main') ?></h1>
 
-	<div class="stat-grid">
-		<div class="stat half">
-			<h2><?= _t('admin.stats.entry_repartition') ?></h2>
-			<div class="table-wrapper scrollbar-thin">
-				<table>
-					<thead>
-						<tr>
-							<th> </th>
-							<th><?= _t('admin.stats.main_stream') ?></th>
-							<th><?= _t('admin.stats.all_feeds') ?></th>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<th><?= _t('admin.stats.status_total') ?></th>
-							<td class="numeric"><?= format_number($this->repartitions['main_stream']['total'] ?? -1) ?></td>
-							<td class="numeric"><?= format_number($this->repartitions['all_feeds']['total'] ?? -1) ?></td>
-						</tr>
-						<tr>
-							<th><?= _t('admin.stats.status_read') ?></th>
-							<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_reads'] ?? -1) ?></td>
-							<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_reads'] ?? -1) ?></td>
-						</tr>
-						<tr>
-							<th><?= _t('admin.stats.status_unread') ?></th>
-							<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_unreads'] ?? -1) ?></td>
-							<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_unreads'] ?? -1) ?></td>
-						</tr>
-						<tr>
-							<th><?= _t('admin.stats.status_favorites') ?></th>
-							<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_favorites'] ?? -1) ?></td>
-							<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_favorites'] ?? -1) ?></td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
+	<div class="box">
+		<div class="box-title"><h2><?= _t('admin.stats.entry_repartition') ?></h2></div>
+		<div class="box-content scrollbar-thin">
+			<table>
+				<thead>
+					<tr>
+						<th> </th>
+						<th><?= _t('admin.stats.main_stream') ?></th>
+						<th><?= _t('admin.stats.all_feeds') ?></th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<th><?= _t('admin.stats.status_total') ?></th>
+						<td class="numeric"><?= format_number($this->repartitions['main_stream']['total'] ?? -1) ?></td>
+						<td class="numeric"><?= format_number($this->repartitions['all_feeds']['total'] ?? -1) ?></td>
+					</tr>
+					<tr>
+						<th><?= _t('admin.stats.status_read') ?></th>
+						<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_reads'] ?? -1) ?></td>
+						<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_reads'] ?? -1) ?></td>
+					</tr>
+					<tr>
+						<th><?= _t('admin.stats.status_unread') ?></th>
+						<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_unreads'] ?? -1) ?></td>
+						<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_unreads'] ?? -1) ?></td>
+					</tr>
+					<tr>
+						<th><?= _t('admin.stats.status_favorites') ?></th>
+						<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_favorites'] ?? -1) ?></td>
+						<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_favorites'] ?? -1) ?></td>
+					</tr>
+				</tbody>
+			</table>
 		</div>
+	</div>
+
 
-		<div class="stat half">
-			<h2><?= _t('admin.stats.top_feed') ?></h2>
-			<div class="table-wrapper scrollbar-thin">
-				<table>
-					<thead>
+	<div class="box double-height">
+		<div class="box-title"><h2><?= _t('admin.stats.top_feed') ?></h2></div>
+		<div class="box-content scrollbar-thin">
+			<table>
+				<thead>
+					<tr>
+						<th><?= _t('admin.stats.feed') ?></th>
+						<th><?= _t('admin.stats.category') ?></th>
+						<th><?= _t('admin.stats.entry_count') ?></th>
+						<th><?= _t('admin.stats.percent_of_total') ?></th>
+					</tr>
+				</thead>
+				<tbody>
+					<?php foreach ($this->topFeed as $feed): ?>
 						<tr>
-							<th><?= _t('admin.stats.feed') ?></th>
-							<th><?= _t('admin.stats.category') ?></th>
-							<th><?= _t('admin.stats.entry_count') ?></th>
-							<th><?= _t('admin.stats.percent_of_total') ?></th>
+							<td><a href="<?= _url('stats', 'repartition', 'id', $feed['id']) ?>"><?= $feed['name'] ?></a></td>
+							<td><?= $feed['category'] ?></td>
+							<td class="numeric"><?= format_number($feed['count']) ?></td>
+							<td class="numeric"><?php
+								if (!empty($this->repartitions['all_feeds']['total'])) {
+									echo format_number($feed['count'] / $this->repartitions['all_feeds']['total'] * 100, 1);
+								}
+							?></td>
 						</tr>
-					</thead>
-					<tbody>
-						<?php foreach ($this->topFeed as $feed): ?>
-							<tr>
-								<td><a href="<?= _url('stats', 'repartition', 'id', $feed['id']) ?>"><?= $feed['name'] ?></a></td>
-								<td><?= $feed['category'] ?></td>
-								<td class="numeric"><?= format_number($feed['count']) ?></td>
-								<td class="numeric"><?php
-									if (!empty($this->repartitions['all_feeds']['total'])) {
-										echo format_number($feed['count'] / $this->repartitions['all_feeds']['total'] * 100, 1);
-									}
-								?></td>
-							</tr>
-						<?php endforeach; ?>
-					</tbody>
-				</table>
-			</div>
+					<?php endforeach; ?>
+				</tbody>
+			</table>
 		</div>
-
-		<div class="stat">
-			<h2><?= _t('admin.stats.entry_per_day') ?></h2>
-			<div>
+	</div>
+	<br />
+		<div class="box double-width double-height">
+			<div class="box-title"><h2><?= _t('admin.stats.entry_per_day') ?></h2></div>
+			<div class="box-content scrollbar-thin">
 				<canvas id="statsEntriesPerDay"></canvas>
 				<script class="jsonData-stats" type="application/json">
 				<?= json_encode([
@@ -140,10 +140,10 @@ $entryLabels = array_keys($entryData);
 $entryColors = array_map(fn($label) => $colorMap[$label], $entryLabels);
 $entryValues = array_values($entryData);
 ?>
-
-		<div class="stat half">
-			<h2><?= _t('admin.stats.feed_per_category') ?></h2>
-			<div>
+<br id="stats_per_category" />
+		<div class="box double-height" id="feed_per_category">
+			<div class="box-title"><h2><?= _t('admin.stats.feed_per_category') ?></h2><a href="#feed_per_category" class="btn target-hidden">+</a><a href="#stats_per_category" class="btn target-visible">-</a></div>
+			<div class="box-content scrollbar-thin">
 				<canvas id="statsFeedsPerCategory"></canvas>
 				<script class="jsonData-stats" type="application/json">
 				<?= json_encode([
@@ -157,9 +157,9 @@ $entryValues = array_values($entryData);
 			</div>
 		</div>
 
-		<div class="stat half">
-			<h2><?= _t('admin.stats.entry_per_category') ?></h2>
-			<div>
+		<div class="box double-height" id="entry_per_category">
+			<div class="box-title"><h2><?= _t('admin.stats.entry_per_category') ?></h2><a href="#entry_per_category" class="btn target-hidden">+</a><a href="#stats_per_category" class="btn target-visible">-</a></div>
+			<div class="box-content scrollbar-thin">
 				<canvas id="statsEntriesPerCategory"></canvas>
 				<script class="jsonData-stats" type="application/json">
 				<?= json_encode([

+ 76 - 56
app/views/stats/repartition.phtml

@@ -2,9 +2,11 @@
 	declare(strict_types=1);
 	/** @var FreshRSS_ViewStats $this */
 	$this->partial('aside_subscription');
+	$feedname = _t('admin.stats.all_feeds');
 ?>
-<main class="post ">
-	<h1><?= _t('admin.stats.repartition') ?></h1>
+
+<nav class="nav_menu">
+	<a class="btn toggle_aside" href="#aside_feed"><?= _i('category') ?></a>
 
 	<select id="feed_select" class="select-change">
 		<option data-url="<?= _url('stats', 'repartition') ?>"><?= _t('admin.stats.all_feeds') ?></option>
@@ -16,6 +18,7 @@
 				if ($this->feed !== null && $feed->id() == $this->feed->id()) {
 					echo '<option value="', $feed->id(), '" selected="selected" data-url="',
 						_url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
+					$feedname = $feed->name();
 				} else {
 					echo '<option value="', $feed->id(), '" data-url="',
 						_url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
@@ -31,74 +34,91 @@
 			<?= _i('configure') ?> <?= _t('gen.action.manage') ?>
 		</a>
 	<?php }?>
+</nav>
+
+<main class="post">
+	<h1><?= _t('admin.stats.repartition', $feedname) ?></h1>
 
-	<div class="stat-grid">
-		<div class="stat table-wrapper scrollbar-thin">
+	<div class="box double-width">
+		<div class="box-title"><h2><?= _t('admin.stats.overview') ?></h2></div>
+		<div class="box-content scrollbar-thin">
 			<table>
 			<tr>
 				<th><?= _t('admin.stats.status_total') ?></th>
-				<th><?= _t('admin.stats.status_read') ?></th>
-				<th><?= _t('admin.stats.status_unread') ?></th>
-				<th><?= _t('admin.stats.status_favorites') ?></th>
+				<th><?= _i('read') ?> <?= _t('admin.stats.status_read') ?></th>
+				<th><?= _i('unread') ?> <?= _t('admin.stats.status_unread') ?></th>
+				<th><?= _i('starred') ?> <?= _t('admin.stats.status_favorites') ?></th>
 			</tr>
 			<tr>
-				<td class="numeric"><?= $this->repartition['total'] ?? -1 ?></td>
-				<td class="numeric"><?= $this->repartition['count_reads'] ?? -1 ?></td>
-				<td class="numeric"><?= $this->repartition['count_unreads'] ?? -1 ?></td>
-				<td class="numeric"><?= $this->repartition['count_favorites'] ?? -1 ?></td>
+				<?php
+				$feedID = $this->feed !== null ? $this->feed->id() : 0;
+				if ($feedID === 0) { ?>
+					<td class="numeric"><?= $this->repartition['total'] ?? -1 ?></td>
+					<td class="numeric"><?= $this->repartition['count_reads'] ?? -1 ?></td>
+					<td class="numeric"><?= $this->repartition['count_unreads'] ?? -1 ?></td>
+					<td class="numeric"><a href="<?= _url('index', 'index', 'get', 's') ?>" title="<?= _t('gen.action.filter') ?>"><?= $this->repartition['count_favorites'] ?? -1 ?></a></td>
+				<?php
+				} else {
+				?>
+					<td class="numeric"><a href="<?= _url('index', 'index', 'get', 'f_' . $feedID, 'state', FreshRSS_Entry::STATE_ALL) ?>" title="<?= _t('gen.action.filter') ?>"><?= $this->repartition['total'] ?? -1 ?></a></td>
+					<td class="numeric"><a href="<?= _url('index', 'index', 'get', 'f_' . $feedID, 'state', FreshRSS_Entry::STATE_READ) ?>" title="<?= _t('gen.action.filter') ?>"><?= $this->repartition['count_reads'] ?? -1 ?></a></td>
+					<td class="numeric"><a href="<?= _url('index', 'index', 'get', 'f_' . $feedID, 'state', FreshRSS_Entry::STATE_NOT_READ) ?>" title="<?= _t('gen.action.filter') ?>"><?= $this->repartition['count_unreads'] ?? -1 ?></a></td>
+					<td class="numeric"><a href="<?= _url('index', 'index', 'get', 'f_' . $feedID, 'state', FreshRSS_Entry::STATE_FAVORITE) ?>" title="<?= _t('gen.action.filter') ?>"><?= $this->repartition['count_favorites'] ?? -1 ?></a></td>
+				<?php } ?>
 			</tr>
 			</table>
 		</div>
+	</div>
 
-		<div class="stat">
-			<h2><?= _t('admin.stats.entry_per_hour', $this->averageHour) ?></h2>
-			<div>
-				<canvas id="statsEntriesPerHour"></canvas>
-				<script class="jsonData-stats" type="application/json">
-				<?php
-				echo json_encode([
-					'canvasID' 		=> 'statsEntriesPerHour',
-					'charttype' 	=> 'bar',
-					'data' 			=> $this->repartitionHour,
-					'label' 		=> _t('admin.stats.entry_count'),
-					'xAxisLabels' 	=> $this->hours24Labels
-				], JSON_UNESCAPED_UNICODE);
-				?></script>
-			</div>
+	<div class="box double-width double-height">
+		<div class="box-title"><h2><?= _t('admin.stats.entry_per_hour', $this->averageHour) ?></h2></div>
+		<div class="box-content scrollbar-thin">
+			<canvas id="statsEntriesPerHour"></canvas>
+			<script class="jsonData-stats" type="application/json">
+			<?php
+			echo json_encode([
+				'canvasID' 		=> 'statsEntriesPerHour',
+				'charttype' 	=> 'bar',
+				'data' 			=> $this->repartitionHour,
+				'label' 		=> _t('admin.stats.entry_count'),
+				'xAxisLabels' 	=> $this->hours24Labels
+			], JSON_UNESCAPED_UNICODE);
+			?></script>
 		</div>
+	</div>
 
-		<div class="stat half">
-			<h2><?= _t('admin.stats.entry_per_day_of_week', $this->averageDayOfWeek) ?></h2>
-			<div>
-				<canvas id="statsEntriesPerDayOfWeek"></canvas>
-				<script class="jsonData-stats" type="application/json">
-				<?php
-				echo json_encode([
-					'canvasID' 		=> 'statsEntriesPerDayOfWeek',
-					'charttype' 	=> 'bar',
-					'data' 			=> $this->repartitionDayOfWeek,
-					'label' 		=> _t('admin.stats.entry_count'),
-					'xAxisLabels' 	=> $this->days,
-				], JSON_UNESCAPED_UNICODE);
-				?></script>
-			</div>
+	<br />
+	<div class="box">
+		<div class="box-title"><h2><?= _t('admin.stats.entry_per_day_of_week', $this->averageDayOfWeek) ?></h2></div>
+		<div class="box-content scrollbar-thin">
+			<canvas id="statsEntriesPerDayOfWeek"></canvas>
+			<script class="jsonData-stats" type="application/json">
+			<?php
+			echo json_encode([
+				'canvasID' 		=> 'statsEntriesPerDayOfWeek',
+				'charttype' 	=> 'bar',
+				'data' 			=> $this->repartitionDayOfWeek,
+				'label' 		=> _t('admin.stats.entry_count'),
+				'xAxisLabels' 	=> $this->days,
+			], JSON_UNESCAPED_UNICODE);
+			?></script>
 		</div>
+	</div>
 
-		<div class="stat half">
-			<h2><?= _t('admin.stats.entry_per_month', $this->averageMonth) ?></h2>
-			<div>
-				<canvas id="statsEntriesPerMonth"></canvas>
-				<script class="jsonData-stats" type="application/json">
-				<?php
-				echo json_encode([
-					'canvasID' 		=> 'statsEntriesPerMonth',
-					'charttype' 	=> 'bar',
-					'data' 			=> $this->repartitionMonth,
-					'label' 		=> _t('admin.stats.entry_count'),
-					'xAxisLabels' 	=> $this->months,
-				], JSON_UNESCAPED_UNICODE);
-				?></script>
-			</div>
+	<div class="box">
+		<div class="box-title"><h2><?= _t('admin.stats.entry_per_month', $this->averageMonth) ?></h2></div>
+		<div class="box-content scrollbar-thin">
+			<canvas id="statsEntriesPerMonth"></canvas>
+			<script class="jsonData-stats" type="application/json">
+			<?php
+			echo json_encode([
+				'canvasID' 		=> 'statsEntriesPerMonth',
+				'charttype' 	=> 'bar',
+				'data' 			=> $this->repartitionMonth,
+				'label' 		=> _t('admin.stats.entry_count'),
+				'xAxisLabels' 	=> $this->months,
+			], JSON_UNESCAPED_UNICODE);
+			?></script>
 		</div>
 	</div>
 </main>

+ 52 - 25
p/themes/base-theme/frss.css

@@ -933,13 +933,34 @@ input[type="checkbox"]:focus-visible {
 
 /*=== Boxes */
 .box {
-	margin: 20px 20px 20px 0;
+	margin: 1.25rem 1.25rem 1.25rem 0;
 	display: inline-block;
 	max-width: 95%;
 	width: 30rem;
 	vertical-align: top;
 }
 
+.box.double-width,
+.box:target {
+	width: 61.25rem;
+}
+
+.box .target-hidden {
+	display: initial;
+}
+
+.box:target .target-hidden {
+	display: none;
+}
+
+.box .target-visible {
+	display: none;
+}
+
+.box:target .target-visible {
+	display: initial;
+}
+
 .box.visible-semi {
 	border-style: dashed;
 	opacity: 0.5;
@@ -972,6 +993,18 @@ input[type="checkbox"]:focus-visible {
 	overflow: auto;
 }
 
+.box .box-content table {
+	width: calc(100% - 1.25rem);
+}
+
+.box.double-height .box-content {
+	max-height: 520px;
+}
+
+.box:target .box-content {
+	max-height: fit-content;
+}
+
 .box .box-content .feed.item {
 	font-size: 0.9rem;
 	line-height: 1.5;
@@ -1865,26 +1898,13 @@ a.website:hover .favicon {
 }
 
 /*=== Statistiques */
-.stat-grid {
-	display: grid;
-	grid-template-columns: 1fr 1fr;
-	grid-gap: 20px;
-}
-
-.stat {
-	grid-column: 1 / span 2;
-}
-
-.stat.half {
-	grid-column: auto;
-}
-
-.stat > table {
-	width: 100%;
-}
-
-.statGraph {
-	height: 300px;
+.box .box-title .btn {
+	position: absolute;
+	right: 1rem;
+	line-height: 1;
+	min-height: 1rem;
+	padding: 0.25rem;
+	text-align: center;
 }
 
 /*=== GLOBAL VIEW */
@@ -2292,10 +2312,21 @@ html.slider-active {
 .nav_menu {
 	padding-top: var(--frss-padding-top-bottom);
 	padding-bottom: var(--frss-padding-top-bottom);
+	padding-left: 0.5rem;
+	padding-right: 0.5rem;
 	background: inherit;
 	text-align: center;
 }
 
+.nav_menu .stick {
+	max-width: 500px;
+}
+
+.nav_menu select {
+	max-width: 300px;
+	width: 60%;
+}
+
 .nav_mobile {
 	display: none;
 }
@@ -2692,10 +2723,6 @@ html.slider-active {
 	#slider.active:target + #close-slider {
 		display: none;
 	}
-
-	.stat.half {
-		grid-column: 1 / span 2;
-	}
 }
 
 /*=== PRINTER */

+ 52 - 25
p/themes/base-theme/frss.rtl.css

@@ -933,13 +933,34 @@ input[type="checkbox"]:focus-visible {
 
 /*=== Boxes */
 .box {
-	margin: 20px 0 20px 20px;
+	margin: 1.25rem 0 1.25rem 1.25rem;
 	display: inline-block;
 	max-width: 95%;
 	width: 30rem;
 	vertical-align: top;
 }
 
+.box.double-width,
+.box:target {
+	width: 61.25rem;
+}
+
+.box .target-hidden {
+	display: initial;
+}
+
+.box:target .target-hidden {
+	display: none;
+}
+
+.box .target-visible {
+	display: none;
+}
+
+.box:target .target-visible {
+	display: initial;
+}
+
 .box.visible-semi {
 	border-style: dashed;
 	opacity: 0.5;
@@ -972,6 +993,18 @@ input[type="checkbox"]:focus-visible {
 	overflow: auto;
 }
 
+.box .box-content table {
+	width: calc(100% - 1.25rem);
+}
+
+.box.double-height .box-content {
+	max-height: 520px;
+}
+
+.box:target .box-content {
+	max-height: fit-content;
+}
+
 .box .box-content .feed.item {
 	font-size: 0.9rem;
 	line-height: 1.5;
@@ -1865,26 +1898,13 @@ a.website:hover .favicon {
 }
 
 /*=== Statistiques */
-.stat-grid {
-	display: grid;
-	grid-template-columns: 1fr 1fr;
-	grid-gap: 20px;
-}
-
-.stat {
-	grid-column: 1 / span 2;
-}
-
-.stat.half {
-	grid-column: auto;
-}
-
-.stat > table {
-	width: 100%;
-}
-
-.statGraph {
-	height: 300px;
+.box .box-title .btn {
+	position: absolute;
+	left: 1rem;
+	line-height: 1;
+	min-height: 1rem;
+	padding: 0.25rem;
+	text-align: center;
 }
 
 /*=== GLOBAL VIEW */
@@ -2292,10 +2312,21 @@ html.slider-active {
 .nav_menu {
 	padding-top: var(--frss-padding-top-bottom);
 	padding-bottom: var(--frss-padding-top-bottom);
+	padding-right: 0.5rem;
+	padding-left: 0.5rem;
 	background: inherit;
 	text-align: center;
 }
 
+.nav_menu .stick {
+	max-width: 500px;
+}
+
+.nav_menu select {
+	max-width: 300px;
+	width: 60%;
+}
+
 .nav_mobile {
 	display: none;
 }
@@ -2692,10 +2723,6 @@ html.slider-active {
 	#slider.active:target + #close-slider {
 		display: none;
 	}
-
-	.stat.half {
-		grid-column: 1 / span 2;
-	}
 }
 
 /*=== PRINTER */