Skip to content

Commit 27a53d0

Browse files
authored
Merge pull request #147 from flutter-news-app-full-source-code/feat/display-key-performance-metrics
Feat/display key performance metrics
2 parents e2b6f41 + a15e358 commit 27a53d0

29 files changed

+2819
-24
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<p align="center">
88
<a href="https://flutter-news-app-full-source-code.github.io/flutter-news-app-web-dashboard-full-source-code/"><img src="https://img.shields.io/badge/LIVE_DEMO-VIEW-orange?style=for-the-badge" alt="Live Demo: View"></a>
99
<a href="https://flutter-news-app-full-source-code.github.io/docs/web-dashboard/local-setup/"><img src="https://img.shields.io/badge/DOCUMENTATION-READ-slategray?style=for-the-badge" alt="Documentation: Read"></a>
10-
<img src="https://img.shields.io/badge/coverage-_%25-red?style=for-the-badge" alt="">
10+
<!-- <img src="https://img.shields.io/badge/coverage-_%25-red?style=for-the-badge" alt=""> -->
1111
</p>
1212
<p align="center">
1313
<a href="LICENSE"><img src="https://img.shields.io/badge/TRIAL_LICENSE-VIEW_TERMS-blue?style=for-the-badge" alt="Trial License: View Terms"></a>
@@ -22,13 +22,25 @@ This dashboard provides a complete, production-ready command center for your ent
2222

2323
Explore the high-level domains below to see how.
2424

25+
<details>
26+
<summary><strong>📊 Operational Intelligence</strong></summary>
27+
28+
### 📈 Dashboard Overview
29+
A centralized command center providing a real-time pulse on your entire news operation.
30+
- **Unified Business Intelligence:** View pre-aggregated metrics that combine user behavior data with operational stats for a holistic performance picture.
31+
- **High-Performance Visualization:** Visualize growth and trends with interactive charts that load instantly, powered by an optimized ETL backend engine.
32+
- **Top Content Ranking:** Instantly identify your highest-performing headlines, sources, and topics to double down on what works.
33+
> **Your Advantage:** Move from reactive management to proactive strategy. The dashboard delivers fast, actionable insights without the latency of direct provider queries, helping you spot trends early and optimize your content strategy.
34+
35+
</details>
36+
2537
<details>
2638
<summary><strong>✍️ Content & Editorial Management</strong></summary>
2739

2840
### 📰 Complete Editorial Control
2941
Manage the entire lifecycle of your content from a single, intuitive interface. This is more than just a database editor; it's a complete content operations hub.
3042
- **Full Content Lifecycle:** Seamlessly draft, publish, edit, archive, and restore all content assets, including headlines, topics, and news sources.
31-
- **At-a-Glance Operational Overview:** A centralized dashboard provides a real-time snapshot of your content ecosystem, including key statistics and shortcuts for common editorial tasks.
43+
- **Contextual Performance Metrics:** Make informed editorial decisions with real-time views, likes, and engagement data integrated directly into your content lists.
3244
> **Your Advantage:** Gain granular control over your entire content pipeline. This centralized system streamlines your editorial workflow, ensures content consistency, and simplifies asset management.
3345
3446
</details>
@@ -39,6 +51,7 @@ Manage the entire lifecycle of your content from a single, intuitive interface.
3951
### 👥 Granular User & Role Management
4052
Effortlessly manage your entire user base with a dedicated user management system. View all registered users, filter them by email or role, and dynamically adjust their dashboard permissions.
4153
- **Full User Roster:** See a comprehensive list of all users, including their email, app subscription level, and current dashboard role.
54+
- **User Growth Insights:** Track registration trends and active user metrics alongside your user roster to understand audience growth.
4255
- **Dynamic Role Promotion:** Promote trusted users to a "Publisher" role, granting them content management capabilities without full administrative access.
4356
- **Powerful Filtering:** Quickly locate specific users or user segments with multi-faceted filtering by email, app role, and dashboard role.
4457
> **Your Advantage:** Delegate content creation responsibilities securely, build out your editorial team, and maintain a clear overview of all system users and their permissions, all from a single, centralized interface.
@@ -51,6 +64,7 @@ Effortlessly manage your entire user base with a dedicated user management syste
5164
### 💬 Comprehensive Moderation Hub
5265
Directly manage all user-generated content from a centralized command center. Review, moderate, and act on user interactions to maintain a healthy and constructive community environment.
5366
- **Unified Content Review:** Seamlessly moderate all incoming user engagements (reactions and comments), content reports, and app review feedback from a single, intuitive interface.
67+
- **Community Health Monitoring:** Visualize engagement rates and report resolution times to maintain a healthy community ecosystem.
5468
- **Streamlined Moderation Workflow:** Quickly approve or reject comments, resolve user-submitted reports, and analyze feedback with a consistent set of tools designed for rapid decision-making.
5569
- **Direct User Insight:** Gain a clear, unfiltered view of user sentiment, content issues, and overall satisfaction by directly engaging with their feedback and reports.
5670
> **Your Advantage:** Foster a positive community, protect your brand by quickly addressing problematic content, and gather valuable user insights to improve your content strategy, all from one integrated hub.

lib/app/view/app.dart

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme
1818
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/sources_filter/sources_filter_bloc.dart';
1919
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/topics_filter/topics_filter_bloc.dart';
2020
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
21-
// import 'package:flutter_news_app_web_dashboard_full_source_code/overview/bloc/overview_bloc.dart';
2221
import 'package:flutter_news_app_web_dashboard_full_source_code/router/router.dart';
23-
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart';
24-
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_updates_service.dart';
2522
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
2623
import 'package:flutter_news_app_web_dashboard_full_source_code/user_management/bloc/user_filter/user_filter_bloc.dart';
2724
import 'package:flutter_news_app_web_dashboard_full_source_code/user_management/bloc/user_management_bloc.dart';
@@ -46,6 +43,7 @@ class App extends StatelessWidget {
4643
required DataRepository<Engagement> engagementsRepository,
4744
required DataRepository<Report> reportsRepository,
4845
required DataRepository<AppReview> appReviewsRepository,
46+
required AnalyticsService analyticsService,
4947
required KVStorageService storageService,
5048
required AppEnvironment environment,
5149
required PendingDeletionsService pendingDeletionsService,
@@ -64,6 +62,7 @@ class App extends StatelessWidget {
6462
_engagementsRepository = engagementsRepository,
6563
_reportsRepository = reportsRepository,
6664
_appReviewsRepository = appReviewsRepository,
65+
_analyticsService = analyticsService,
6766
_environment = environment,
6867
_pendingDeletionsService = pendingDeletionsService;
6968

@@ -81,6 +80,7 @@ class App extends StatelessWidget {
8180
final DataRepository<Engagement> _engagementsRepository;
8281
final DataRepository<Report> _reportsRepository;
8382
final DataRepository<AppReview> _appReviewsRepository;
83+
final AnalyticsService _analyticsService;
8484
final KVStorageService _kvStorageService;
8585
final AppEnvironment _environment;
8686

@@ -104,6 +104,7 @@ class App extends StatelessWidget {
104104
RepositoryProvider.value(value: _engagementsRepository),
105105
RepositoryProvider.value(value: _reportsRepository),
106106
RepositoryProvider.value(value: _appReviewsRepository),
107+
RepositoryProvider.value(value: _analyticsService),
107108
RepositoryProvider.value(value: _kvStorageService),
108109
RepositoryProvider(
109110
create: (context) => const ThrottledFetchingService(),
@@ -159,13 +160,6 @@ class App extends StatelessWidget {
159160
pendingDeletionsService: context.read<PendingDeletionsService>(),
160161
),
161162
),
162-
// BlocProvider(
163-
// create: (context) => OverviewBloc(
164-
// headlinesRepository: context.read<DataRepository<Headline>>(),
165-
// topicsRepository: context.read<DataRepository<Topic>>(),
166-
// sourcesRepository: context.read<DataRepository<Source>>(),
167-
// ),
168-
// ),
169163
// The UserFilterBloc is provided here to be available for both the
170164
// UserManagementBloc and the UI components.
171165
BlocProvider(create: (_) => UserFilterBloc()),

lib/bootstrap.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app/app.dart';
1313
import 'package:flutter_news_app_web_dashboard_full_source_code/app/config/config.dart'
1414
as app_config;
1515
import 'package:flutter_news_app_web_dashboard_full_source_code/bloc_observer.dart';
16+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/app_constants.dart';
17+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/analytics_service.dart';
1618
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart';
1719
import 'package:http_client/http_client.dart';
1820
import 'package:kv_storage_shared_preferences/kv_storage_shared_preferences.dart';
@@ -27,6 +29,9 @@ Future<Widget> bootstrap(
2729
WidgetsFlutterBinding.ensureInitialized();
2830
Bloc.observer = const AppBlocObserver();
2931

32+
// Validate application constants to ensure layout integrity.
33+
AppConstants.validate();
34+
3035
timeago.setLocaleMessages('en', EnTimeagoMessages());
3136
timeago.setLocaleMessages('ar', ArTimeagoMessages());
3237

@@ -67,6 +72,9 @@ Future<Widget> bootstrap(
6772
DataClient<Engagement> engagementsClient;
6873
DataClient<Report> reportsClient;
6974
DataClient<AppReview> appReviewsClient;
75+
DataClient<KpiCardData> kpiCardsClient;
76+
DataClient<ChartCardData> chartCardsClient;
77+
DataClient<RankedListCardData> rankedListCardsClient;
7078

7179
if (appConfig.environment == app_config.AppEnvironment.demo) {
7280
headlinesClient = DataInMemory<Headline>(
@@ -142,6 +150,24 @@ Future<Widget> bootstrap(
142150
initialData: getAppReviewsFixturesData(),
143151
logger: Logger('DataInMemory<AppReview>'),
144152
);
153+
kpiCardsClient = DataInMemory<KpiCardData>(
154+
toJson: (i) => i.toJson(),
155+
getId: (i) => i.id.name,
156+
initialData: getKpiCardsFixturesData(),
157+
logger: Logger('DataInMemory<KpiCardData>'),
158+
);
159+
chartCardsClient = DataInMemory<ChartCardData>(
160+
toJson: (i) => i.toJson(),
161+
getId: (i) => i.id.name,
162+
initialData: getChartCardsFixturesData(),
163+
logger: Logger('DataInMemory<ChartCardData>'),
164+
);
165+
rankedListCardsClient = DataInMemory<RankedListCardData>(
166+
toJson: (i) => i.toJson(),
167+
getId: (i) => i.id.name,
168+
initialData: getRankedListCardsFixturesData(),
169+
logger: Logger('DataInMemory<RankedListCardData>'),
170+
);
145171
} else {
146172
headlinesClient = DataApi<Headline>(
147173
httpClient: httpClient!,
@@ -228,6 +254,27 @@ Future<Widget> bootstrap(
228254
toJson: (appReview) => appReview.toJson(),
229255
logger: Logger('DataApi<AppReview>'),
230256
);
257+
kpiCardsClient = DataApi<KpiCardData>(
258+
httpClient: httpClient,
259+
modelName: 'kpi_card',
260+
fromJson: KpiCardData.fromJson,
261+
toJson: (item) => item.toJson(),
262+
logger: Logger('DataApi<KpiCardData>'),
263+
);
264+
chartCardsClient = DataApi<ChartCardData>(
265+
httpClient: httpClient,
266+
modelName: 'chart_card',
267+
fromJson: ChartCardData.fromJson,
268+
toJson: (item) => item.toJson(),
269+
logger: Logger('DataApi<ChartCardData>'),
270+
);
271+
rankedListCardsClient = DataApi<RankedListCardData>(
272+
httpClient: httpClient,
273+
modelName: 'ranked_list_card',
274+
fromJson: RankedListCardData.fromJson,
275+
toJson: (item) => item.toJson(),
276+
logger: Logger('DataApi<RankedListCardData>'),
277+
);
231278
}
232279

233280
pendingDeletionsService = PendingDeletionsServiceImpl(
@@ -265,6 +312,21 @@ Future<Widget> bootstrap(
265312
final appReviewsRepository = DataRepository<AppReview>(
266313
dataClient: appReviewsClient,
267314
);
315+
final kpiCardsRepository = DataRepository<KpiCardData>(
316+
dataClient: kpiCardsClient,
317+
);
318+
final chartCardsRepository = DataRepository<ChartCardData>(
319+
dataClient: chartCardsClient,
320+
);
321+
final rankedListCardsRepository = DataRepository<RankedListCardData>(
322+
dataClient: rankedListCardsClient,
323+
);
324+
325+
final analyticsService = AnalyticsService(
326+
kpiRepository: kpiCardsRepository,
327+
chartRepository: chartCardsRepository,
328+
rankedListRepository: rankedListCardsRepository,
329+
);
268330

269331
return App(
270332
authenticationRepository: authenticationRepository,
@@ -280,6 +342,7 @@ Future<Widget> bootstrap(
280342
engagementsRepository: engagementsRepository,
281343
reportsRepository: reportsRepository,
282344
appReviewsRepository: appReviewsRepository,
345+
analyticsService: analyticsService,
283346
storageService: kvStorage,
284347
environment: environment,
285348
pendingDeletionsService: pendingDeletionsService,

lib/community_management/view/app_reviews_page.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
88
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
99
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
1010
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_review_feedback_extension.dart';
11+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
1112
import 'package:intl/intl.dart';
1213
import 'package:ui_kit/ui_kit.dart';
1314

@@ -103,6 +104,19 @@ class _AppReviewsPageState extends State<AppReviewsPage> {
103104

104105
return Column(
105106
children: [
107+
// Analytics Dashboard Strip
108+
const AnalyticsDashboardStrip(
109+
kpiCards: [
110+
KpiCardId.engagementsAppReviewsTotalFeedback,
111+
KpiCardId.engagementsAppReviewsPositiveFeedback,
112+
KpiCardId.engagementsAppReviewsStoreRequests,
113+
],
114+
chartCards: [
115+
ChartCardId.engagementsAppReviewsFeedbackOverTime,
116+
ChartCardId.engagementsAppReviewsPositiveVsNegative,
117+
ChartCardId.engagementsAppReviewsStoreRequestsOverTime,
118+
],
119+
),
106120
if (state.appReviewsStatus == CommunityManagementStatus.loading &&
107121
state.appReviews.isNotEmpty)
108122
const LinearProgressIndicator(),

lib/community_management/view/engagements_page.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
77
import 'package:flutter_news_app_web_dashboard_full_source_code/community_management/widgets/community_action_buttons.dart';
88
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
99
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
10+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
1011
import 'package:intl/intl.dart';
1112
import 'package:ui_kit/ui_kit.dart';
1213

@@ -102,6 +103,19 @@ class _EngagementsPageState extends State<EngagementsPage> {
102103

103104
return Column(
104105
children: [
106+
// Analytics Dashboard Strip
107+
const AnalyticsDashboardStrip(
108+
kpiCards: [
109+
KpiCardId.engagementsTotalReactions,
110+
KpiCardId.engagementsTotalComments,
111+
KpiCardId.engagementsAverageEngagementRate,
112+
],
113+
chartCards: [
114+
ChartCardId.engagementsReactionsOverTime,
115+
ChartCardId.engagementsCommentsOverTime,
116+
ChartCardId.engagementsReactionsByType,
117+
],
118+
),
105119
if (state.engagementsStatus ==
106120
CommunityManagementStatus.loading &&
107121
state.engagements.isNotEmpty)

lib/community_management/view/reports_page.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
88
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
99
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
1010
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/extensions.dart';
11+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
1112
import 'package:intl/intl.dart';
1213
import 'package:ui_kit/ui_kit.dart';
1314

@@ -98,6 +99,19 @@ class _ReportsPageState extends State<ReportsPage> {
9899

99100
return Column(
100101
children: [
102+
// Analytics Dashboard Strip
103+
const AnalyticsDashboardStrip(
104+
kpiCards: [
105+
KpiCardId.engagementsReportsPending,
106+
KpiCardId.engagementsReportsResolved,
107+
KpiCardId.engagementsReportsAverageResolutionTime,
108+
],
109+
chartCards: [
110+
ChartCardId.engagementsReportsSubmittedOverTime,
111+
ChartCardId.engagementsReportsResolutionTimeOverTime,
112+
ChartCardId.engagementsReportsByReason,
113+
],
114+
),
101115
if (state.reportsStatus == CommunityManagementStatus.loading &&
102116
state.reports.isNotEmpty)
103117
const LinearProgressIndicator(),

lib/content_management/view/headlines_page.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme
88
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
99
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
1010
import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart';
11+
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
1112
import 'package:go_router/go_router.dart';
1213
import 'package:intl/intl.dart';
1314
import 'package:ui_kit/ui_kit.dart';
@@ -114,6 +115,19 @@ class _HeadlinesPageState extends State<HeadlinesPage> {
114115

115116
return Column(
116117
children: [
118+
// Analytics Dashboard Strip
119+
const AnalyticsDashboardStrip(
120+
kpiCards: [
121+
KpiCardId.contentHeadlinesTotalPublished,
122+
KpiCardId.contentHeadlinesTotalViews,
123+
KpiCardId.contentHeadlinesTotalLikes,
124+
],
125+
chartCards: [
126+
ChartCardId.contentHeadlinesViewsOverTime,
127+
ChartCardId.contentHeadlinesLikesOverTime,
128+
ChartCardId.contentHeadlinesViewsByTopic,
129+
],
130+
),
117131
if (state.headlinesStatus == ContentManagementStatus.loading &&
118132
state.headlines.isNotEmpty)
119133
const LinearProgressIndicator(),

0 commit comments

Comments
 (0)