diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index a2ccb9a..44c6e6f 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -11,6 +11,7 @@ import 'package:ht_dashboard/app/config/app_environment.dart'; import 'package:ht_dashboard/app_configuration/bloc/app_configuration_bloc.dart'; import 'package:ht_dashboard/authentication/bloc/authentication_bloc.dart'; import 'package:ht_dashboard/content_management/bloc/content_management_bloc.dart'; +import 'package:ht_dashboard/dashboard/bloc/dashboard_bloc.dart'; import 'package:ht_dashboard/l10n/app_localizations.dart'; import 'package:ht_dashboard/router/router.dart'; // Import for app_theme.dart @@ -30,6 +31,7 @@ class App extends StatelessWidget { required HtDataRepository htUserContentPreferencesRepository, required HtDataRepository htAppConfigRepository, + required HtDataRepository htDashboardSummaryRepository, required HtKVStorageService kvStorageService, required AppEnvironment environment, super.key, @@ -42,6 +44,7 @@ class App extends StatelessWidget { _htUserContentPreferencesRepository = htUserContentPreferencesRepository, _htAppConfigRepository = htAppConfigRepository, _kvStorageService = kvStorageService, + _htDashboardSummaryRepository = htDashboardSummaryRepository, _environment = environment; final HtAuthRepository _htAuthenticationRepository; @@ -53,6 +56,7 @@ class App extends StatelessWidget { final HtDataRepository _htUserContentPreferencesRepository; final HtDataRepository _htAppConfigRepository; + final HtDataRepository _htDashboardSummaryRepository; final HtKVStorageService _kvStorageService; final AppEnvironment _environment; @@ -68,6 +72,7 @@ class App extends StatelessWidget { RepositoryProvider.value(value: _htUserAppSettingsRepository), RepositoryProvider.value(value: _htUserContentPreferencesRepository), RepositoryProvider.value(value: _htAppConfigRepository), + RepositoryProvider.value(value: _htDashboardSummaryRepository), RepositoryProvider.value(value: _kvStorageService), ], child: MultiBlocProvider( @@ -98,6 +103,14 @@ class App extends StatelessWidget { sourcesRepository: context.read>(), ), ), + BlocProvider( + create: (context) => DashboardBloc( + dashboardSummaryRepository: context + .read>(), + appConfigRepository: context.read>(), + headlinesRepository: context.read>(), + ), + ), ], child: _AppView( htAuthenticationRepository: _htAuthenticationRepository, diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index ea8a2f1..2c1827e 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -61,6 +61,7 @@ Future bootstrap( HtDataClient userContentPreferencesClient; HtDataClient userAppSettingsClient; HtDataClient appConfigClient; + HtDataClient dashboardSummaryClient; if (appConfig.environment == app_config.AppEnvironment.demo) { headlinesClient = HtDataInMemory( @@ -96,6 +97,13 @@ Future bootstrap( getId: (i) => i.id, initialData: [AppConfig.fromJson(appConfigFixtureData)], ); + dashboardSummaryClient = HtDataInMemory( + toJson: (i) => i.toJson(), + getId: (i) => i.id, + initialData: [ + DashboardSummary.fromJson(dashboardSummaryFixtureData), + ], + ); } else if (appConfig.environment == app_config.AppEnvironment.development) { headlinesClient = HtDataApi( httpClient: httpClient!, @@ -139,6 +147,12 @@ Future bootstrap( fromJson: AppConfig.fromJson, toJson: (config) => config.toJson(), ); + dashboardSummaryClient = HtDataApi( + httpClient: httpClient, + modelName: 'dashboard_summary', + fromJson: DashboardSummary.fromJson, + toJson: (summary) => summary.toJson(), + ); } else { headlinesClient = HtDataApi( httpClient: httpClient!, @@ -182,6 +196,12 @@ Future bootstrap( fromJson: AppConfig.fromJson, toJson: (config) => config.toJson(), ); + dashboardSummaryClient = HtDataApi( + httpClient: httpClient, + modelName: 'dashboard_summary', + fromJson: DashboardSummary.fromJson, + toJson: (summary) => summary.toJson(), + ); } final headlinesRepository = HtDataRepository( @@ -204,6 +224,9 @@ Future bootstrap( final appConfigRepository = HtDataRepository( dataClient: appConfigClient, ); + final dashboardSummaryRepository = HtDataRepository( + dataClient: dashboardSummaryClient, + ); return App( htAuthenticationRepository: authenticationRepository, @@ -214,6 +237,7 @@ Future bootstrap( htUserAppSettingsRepository: userAppSettingsRepository, htUserContentPreferencesRepository: userContentPreferencesRepository, htAppConfigRepository: appConfigRepository, + htDashboardSummaryRepository: dashboardSummaryRepository, kvStorageService: kvStorage, environment: environment, ); diff --git a/lib/dashboard/bloc/dashboard_bloc.dart b/lib/dashboard/bloc/dashboard_bloc.dart new file mode 100644 index 0000000..5bfda30 --- /dev/null +++ b/lib/dashboard/bloc/dashboard_bloc.dart @@ -0,0 +1,76 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:ht_data_repository/ht_data_repository.dart'; +import 'package:ht_shared/ht_shared.dart'; + +part 'dashboard_event.dart'; +part 'dashboard_state.dart'; + +/// A BLoC to manage the state of the dashboard. +class DashboardBloc extends Bloc { + /// {@macro dashboard_bloc} + DashboardBloc({ + required HtDataRepository dashboardSummaryRepository, + required HtDataRepository appConfigRepository, + required HtDataRepository headlinesRepository, + }) : _dashboardSummaryRepository = dashboardSummaryRepository, + _appConfigRepository = appConfigRepository, + _headlinesRepository = headlinesRepository, + super(const DashboardState()) { + on(_onDashboardSummaryLoaded); + } + + final HtDataRepository _dashboardSummaryRepository; + final HtDataRepository _appConfigRepository; + final HtDataRepository _headlinesRepository; + + Future _onDashboardSummaryLoaded( + DashboardSummaryLoaded event, + Emitter emit, + ) async { + emit(state.copyWith(status: DashboardStatus.loading)); + try { + // Fetch summary, app config, and recent headlines concurrently + final [ + summaryResponse, + appConfigResponse, + recentHeadlinesResponse, + ] = await Future.wait([ + _dashboardSummaryRepository.read(id: 'dashboard_summary'), + _appConfigRepository.read(id: 'app_config'), + _headlinesRepository.readAll( + sortBy: 'createdAt', + sortOrder: SortOrder.desc, + limit: 5, + ), + ]); + + final summary = summaryResponse as DashboardSummary; + final appConfig = appConfigResponse as AppConfig; + final recentHeadlines = + (recentHeadlinesResponse as PaginatedResponse).items; + emit( + state.copyWith( + status: DashboardStatus.success, + summary: summary, + appConfig: appConfig, + recentHeadlines: recentHeadlines, + ), + ); + } on HtHttpException catch (e) { + emit( + state.copyWith( + status: DashboardStatus.failure, + errorMessage: e.message, + ), + ); + } catch (e) { + emit( + state.copyWith( + status: DashboardStatus.failure, + errorMessage: 'An unknown error occurred: $e', + ), + ); + } + } +} diff --git a/lib/dashboard/bloc/dashboard_event.dart b/lib/dashboard/bloc/dashboard_event.dart new file mode 100644 index 0000000..9b3d391 --- /dev/null +++ b/lib/dashboard/bloc/dashboard_event.dart @@ -0,0 +1,12 @@ +part of 'dashboard_bloc.dart'; + +/// Base class for dashboard events. +sealed class DashboardEvent extends Equatable { + const DashboardEvent(); + + @override + List get props => []; +} + +/// Event to load the dashboard summary data. +final class DashboardSummaryLoaded extends DashboardEvent {} diff --git a/lib/dashboard/bloc/dashboard_state.dart b/lib/dashboard/bloc/dashboard_state.dart new file mode 100644 index 0000000..0bd718e --- /dev/null +++ b/lib/dashboard/bloc/dashboard_state.dart @@ -0,0 +1,58 @@ +part of 'dashboard_bloc.dart'; + +/// Represents the status of the dashboard data loading. +enum DashboardStatus { + /// Initial state. + initial, + + /// Data is being loaded. + loading, + + /// Data has been successfully loaded. + success, + + /// An error occurred while loading data. + failure, +} + +/// The state for the [DashboardBloc]. +final class DashboardState extends Equatable { + const DashboardState({ + this.status = DashboardStatus.initial, + this.summary, + this.appConfig, + this.recentHeadlines = const [], + this.errorMessage, + }); + + final DashboardStatus status; + final DashboardSummary? summary; + final AppConfig? appConfig; + final List recentHeadlines; + final String? errorMessage; + + DashboardState copyWith({ + DashboardStatus? status, + DashboardSummary? summary, + AppConfig? appConfig, + List? recentHeadlines, + String? errorMessage, + }) { + return DashboardState( + status: status ?? this.status, + summary: summary ?? this.summary, + appConfig: appConfig ?? this.appConfig, + recentHeadlines: recentHeadlines ?? this.recentHeadlines, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [ + status, + summary, + appConfig, + recentHeadlines, + errorMessage, + ]; +} diff --git a/lib/dashboard/view/dashboard_page.dart b/lib/dashboard/view/dashboard_page.dart index a0d7d13..007d2eb 100644 --- a/lib/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/view/dashboard_page.dart @@ -1,17 +1,335 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ht_dashboard/dashboard/bloc/dashboard_bloc.dart'; +import 'package:ht_dashboard/l10n/app_localizations.dart'; +import 'package:ht_dashboard/l10n/l10n.dart'; +import 'package:ht_dashboard/router/routes.dart'; +import 'package:ht_dashboard/shared/shared.dart'; +import 'package:ht_shared/ht_shared.dart'; /// {@template dashboard_page} -/// A placeholder page for the dashboard. +/// The main dashboard page, displaying key statistics and quick actions. /// {@endtemplate} -class DashboardPage extends StatelessWidget { +class DashboardPage extends StatefulWidget { /// {@macro dashboard_page} const DashboardPage({super.key}); + @override + State createState() => _DashboardPageState(); +} + +class _DashboardPageState extends State { + @override + void initState() { + super.initState(); + // Dispatch the event to load dashboard data when the page is initialized. + context.read().add(DashboardSummaryLoaded()); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Scaffold( + body: BlocBuilder( + builder: (context, state) { + if (state.status == DashboardStatus.loading || + state.status == DashboardStatus.initial) { + return LoadingStateWidget( + icon: Icons.dashboard_outlined, + headline: l10n.loadingDashboard, + subheadline: l10n.loadingDashboardSubheadline, + ); + } + if (state.status == DashboardStatus.failure) { + return FailureStateWidget( + message: state.errorMessage ?? l10n.dashboardLoadFailure, + onRetry: () { + context.read().add(DashboardSummaryLoaded()); + }, + ); + } + if (state.status == DashboardStatus.success && + state.summary != null && + state.appConfig != null) { + final summary = state.summary!; + final appConfig = state.appConfig!; + final recentHeadlines = state.recentHeadlines; + return ListView( + padding: const EdgeInsets.all(AppSpacing.lg), + children: [ + Wrap( + spacing: AppSpacing.lg, + runSpacing: AppSpacing.lg, + children: [ + _SummaryCard( + icon: Icons.article_outlined, + title: l10n.totalHeadlines, + value: summary.headlineCount.toString(), + ), + _SummaryCard( + icon: Icons.category_outlined, + title: l10n.totalCategories, + value: summary.categoryCount.toString(), + ), + _SummaryCard( + icon: Icons.source_outlined, + title: l10n.totalSources, + value: summary.sourceCount.toString(), + ), + ], + ), + const SizedBox(height: AppSpacing.lg), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: _RecentHeadlinesCard( + headlines: recentHeadlines, + ), + ), + const SizedBox(width: AppSpacing.lg), + Expanded( + flex: 1, + child: Column( + children: [ + _SystemStatusCard( + status: appConfig.appOperationalStatus, + ), + const SizedBox(height: AppSpacing.lg), + const _QuickActionsCard(), + ], + ), + ), + ], + ), + ], + ); + } + return const SizedBox.shrink(); // Fallback for unexpected states + }, + ), + ); + } +} + +/// A card to display the current operational status of the application. +class _SystemStatusCard extends StatelessWidget { + const _SystemStatusCard({required this.status}); + + final RemoteAppStatus status; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + final (icon, color, text) = _getStatusDetails(status, l10n, theme); + + return Card( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.systemStatus, style: theme.textTheme.titleLarge), + const SizedBox(height: AppSpacing.md), + Row( + children: [ + Icon(icon, color: color, size: 24), + const SizedBox(width: AppSpacing.sm), + Text( + text, + style: theme.textTheme.titleMedium?.copyWith(color: color), + ), + ], + ), + ], + ), + ), + ); + } + + /// Returns the appropriate icon, color, and text for a given status. + (IconData, Color, String) _getStatusDetails( + RemoteAppStatus status, + AppLocalizations l10n, + ThemeData theme, + ) { + switch (status) { + case RemoteAppStatus.active: + return ( + Icons.check_circle_outline, + theme.colorScheme.primary, + l10n.appStatusActive, + ); + case RemoteAppStatus.maintenance: + return ( + Icons.warning_amber_outlined, + theme.colorScheme.tertiary, + l10n.appStatusMaintenance, + ); + case RemoteAppStatus.disabled: + return ( + Icons.cancel_outlined, + theme.colorScheme.error, + l10n.appStatusDisabled, + ); + } + } +} + +/// A card providing quick navigation to common administrative tasks. +class _QuickActionsCard extends StatelessWidget { + const _QuickActionsCard(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + return Card( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.quickActions, style: theme.textTheme.titleLarge), + const SizedBox(height: AppSpacing.md), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + icon: const Icon(Icons.add_circle_outline), + label: Text(l10n.createHeadlineAction), + onPressed: () => context.goNamed(Routes.createHeadlineName), + ), + ), + const SizedBox(height: AppSpacing.sm), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + icon: const Icon(Icons.folder_open_outlined), + label: Text(l10n.manageContentAction), + onPressed: () => context.goNamed(Routes.contentManagementName), + ), + ), + const SizedBox(height: AppSpacing.sm), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + icon: const Icon(Icons.settings_applications_outlined), + label: Text(l10n.appConfigAction), + onPressed: () => context.goNamed(Routes.appConfigurationName), + ), + ), + ], + ), + ), + ); + } +} + +/// A card to display a list of recently created headlines. +class _RecentHeadlinesCard extends StatelessWidget { + const _RecentHeadlinesCard({required this.headlines}); + + final List headlines; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + + return Card( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(l10n.recentHeadlines, style: theme.textTheme.titleLarge), + TextButton( + onPressed: () => + context.goNamed(Routes.contentManagementName), + child: Text(l10n.viewAll), + ), + ], + ), + const SizedBox(height: AppSpacing.md), + if (headlines.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), + child: Center( + child: Text( + l10n.noRecentHeadlines, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ) + else + ...headlines.map( + (headline) => ListTile( + leading: const Icon(Icons.article_outlined), + title: Text(headline.title, maxLines: 1), + subtitle: Text( + DateFormatter.formatRelativeTime( + context, + headline.createdAt, + ), + ), + onTap: () => context.goNamed( + Routes.editHeadlineName, + pathParameters: {'id': headline.id}, + ), + contentPadding: EdgeInsets.zero, + dense: true, + ), + ), + ], + ), + ), + ); + } +} + +/// A private widget to display a single summary statistic on the dashboard. +class _SummaryCard extends StatelessWidget { + const _SummaryCard({ + required this.icon, + required this.title, + required this.value, + }); + + final IconData icon; + final String title; + final String value; + @override Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: Text('Welcome to the Dashboard!'), + final theme = Theme.of(context); + return Card( + child: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, size: 28, color: theme.colorScheme.primary), + const SizedBox(height: AppSpacing.sm), + Text(value, style: theme.textTheme.headlineMedium), + const SizedBox(height: AppSpacing.xs), + Text( + title, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ), ), ); } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 16076a4..21205b9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1315,6 +1315,108 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Draft'** String get contentStatusDraft; + + /// Label for the total headlines summary card on the dashboard + /// + /// In en, this message translates to: + /// **'Total Headlines'** + String get totalHeadlines; + + /// Label for the total categories summary card on the dashboard + /// + /// In en, this message translates to: + /// **'Total Categories'** + String get totalCategories; + + /// Label for the total sources summary card on the dashboard + /// + /// In en, this message translates to: + /// **'Total Sources'** + String get totalSources; + + /// Headline for the dashboard loading state + /// + /// In en, this message translates to: + /// **'Loading Dashboard'** + String get loadingDashboard; + + /// Subheadline for the dashboard loading state + /// + /// In en, this message translates to: + /// **'Fetching latest statistics...'** + String get loadingDashboardSubheadline; + + /// Error message when dashboard data fails to load + /// + /// In en, this message translates to: + /// **'Failed to load dashboard data.'** + String get dashboardLoadFailure; + + /// Title for the recent headlines card on the dashboard + /// + /// In en, this message translates to: + /// **'Recent Headlines'** + String get recentHeadlines; + + /// Button text to view all items in a list + /// + /// In en, this message translates to: + /// **'View All'** + String get viewAll; + + /// Message shown when there are no recent headlines + /// + /// In en, this message translates to: + /// **'No recent headlines to display.'** + String get noRecentHeadlines; + + /// Title for the system status card on the dashboard + /// + /// In en, this message translates to: + /// **'System Status'** + String get systemStatus; + + /// Title for the quick actions card on the dashboard + /// + /// In en, this message translates to: + /// **'Quick Actions'** + String get quickActions; + + /// Button text for the create headline quick action + /// + /// In en, this message translates to: + /// **'Create Headline'** + String get createHeadlineAction; + + /// Button text for the manage content quick action + /// + /// In en, this message translates to: + /// **'Manage Content'** + String get manageContentAction; + + /// Button text for the app configuration quick action + /// + /// In en, this message translates to: + /// **'App Configuration'** + String get appConfigAction; + + /// Text for the 'Active' app status + /// + /// In en, this message translates to: + /// **'Active'** + String get appStatusActive; + + /// Text for the 'Maintenance' app status + /// + /// In en, this message translates to: + /// **'Maintenance'** + String get appStatusMaintenance; + + /// Text for the 'Disabled' app status + /// + /// In en, this message translates to: + /// **'Disabled'** + String get appStatusDisabled; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index c98a7e1..90c2537 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -686,4 +686,55 @@ class AppLocalizationsAr extends AppLocalizations { @override String get contentStatusDraft => 'مسودة'; + + @override + String get totalHeadlines => 'إجمالي العناوين'; + + @override + String get totalCategories => 'إجمالي الفئات'; + + @override + String get totalSources => 'إجمالي المصادر'; + + @override + String get loadingDashboard => 'جاري تحميل لوحة القيادة'; + + @override + String get loadingDashboardSubheadline => 'جاري جلب أحدث الإحصائيات...'; + + @override + String get dashboardLoadFailure => 'فشل تحميل بيانات لوحة القيادة.'; + + @override + String get recentHeadlines => 'أحدث العناوين'; + + @override + String get viewAll => 'عرض الكل'; + + @override + String get noRecentHeadlines => 'لا توجد عناوين حديثة لعرضها.'; + + @override + String get systemStatus => 'حالة النظام'; + + @override + String get quickActions => 'إجراءات سريعة'; + + @override + String get createHeadlineAction => 'إنشاء عنوان'; + + @override + String get manageContentAction => 'إدارة المحتوى'; + + @override + String get appConfigAction => 'إعدادات التطبيق'; + + @override + String get appStatusActive => 'نشط'; + + @override + String get appStatusMaintenance => 'صيانة'; + + @override + String get appStatusDisabled => 'معطل'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f34b3fb..e8c93ef 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -684,4 +684,55 @@ class AppLocalizationsEn extends AppLocalizations { @override String get contentStatusDraft => 'Draft'; + + @override + String get totalHeadlines => 'Total Headlines'; + + @override + String get totalCategories => 'Total Categories'; + + @override + String get totalSources => 'Total Sources'; + + @override + String get loadingDashboard => 'Loading Dashboard'; + + @override + String get loadingDashboardSubheadline => 'Fetching latest statistics...'; + + @override + String get dashboardLoadFailure => 'Failed to load dashboard data.'; + + @override + String get recentHeadlines => 'Recent Headlines'; + + @override + String get viewAll => 'View All'; + + @override + String get noRecentHeadlines => 'No recent headlines to display.'; + + @override + String get systemStatus => 'System Status'; + + @override + String get quickActions => 'Quick Actions'; + + @override + String get createHeadlineAction => 'Create Headline'; + + @override + String get manageContentAction => 'Manage Content'; + + @override + String get appConfigAction => 'App Configuration'; + + @override + String get appStatusActive => 'Active'; + + @override + String get appStatusMaintenance => 'Maintenance'; + + @override + String get appStatusDisabled => 'Disabled'; } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 889328f..dc78022 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -825,5 +825,73 @@ "contentStatusArchived": "مؤرشف", "@contentStatusArchived": {}, "contentStatusDraft": "مسودة", - "@contentStatusDraft": {} + "@contentStatusDraft": {}, + "totalHeadlines": "إجمالي العناوين", + "@totalHeadlines": { + "description": "تسمية بطاقة ملخص إجمالي العناوين في لوحة القيادة" + }, + "totalCategories": "إجمالي الفئات", + "@totalCategories": { + "description": "تسمية بطاقة ملخص إجمالي الفئات في لوحة القيادة" + }, + "totalSources": "إجمالي المصادر", + "@totalSources": { + "description": "تسمية بطاقة ملخص إجمالي المصادر في لوحة القيادة" + }, + "loadingDashboard": "جاري تحميل لوحة القيادة", + "@loadingDashboard": { + "description": "عنوان حالة تحميل لوحة القيادة" + }, + "loadingDashboardSubheadline": "جاري جلب أحدث الإحصائيات...", + "@loadingDashboardSubheadline": { + "description": "عنوان فرعي لحالة تحميل لوحة القيادة" + }, + "dashboardLoadFailure": "فشل تحميل بيانات لوحة القيادة.", + "@dashboardLoadFailure": { + "description": "رسالة خطأ عند فشل تحميل بيانات لوحة القيادة" + }, + "recentHeadlines": "أحدث العناوين", + "@recentHeadlines": { + "description": "عنوان بطاقة أحدث العناوين في لوحة القيادة" + }, + "viewAll": "عرض الكل", + "@viewAll": { + "description": "نص زر عرض كل العناصر في قائمة" + }, + "noRecentHeadlines": "لا توجد عناوين حديثة لعرضها.", + "@noRecentHeadlines": { + "description": "رسالة تظهر عند عدم وجود عناوين حديثة" + }, + "systemStatus": "حالة النظام", + "@systemStatus": { + "description": "عنوان بطاقة حالة النظام في لوحة القيادة" + }, + "quickActions": "إجراءات سريعة", + "@quickActions": { + "description": "عنوان بطاقة الإجراءات السريعة في لوحة القيادة" + }, + "createHeadlineAction": "إنشاء عنوان", + "@createHeadlineAction": { + "description": "نص زر الإجراء السريع لإنشاء عنوان" + }, + "manageContentAction": "إدارة المحتوى", + "@manageContentAction": { + "description": "نص زر الإجراء السريع لإدارة المحتوى" + }, + "appConfigAction": "إعدادات التطبيق", + "@appConfigAction": { + "description": "نص زر الإجراء السريع لإعدادات التطبيق" + }, + "appStatusActive": "نشط", + "@appStatusActive": { + "description": "نص حالة التطبيق 'نشط'" + }, + "appStatusMaintenance": "صيانة", + "@appStatusMaintenance": { + "description": "نص حالة التطبيق 'صيانة'" + }, + "appStatusDisabled": "معطل", + "@appStatusDisabled": { + "description": "نص حالة التطبيق 'معطل'" + } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a7b6143..6e7b9f9 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -825,5 +825,73 @@ "contentStatusArchived": "Archived", "@contentStatusArchived": {}, "contentStatusDraft": "Draft", - "@contentStatusDraft": {} + "@contentStatusDraft": {}, + "totalHeadlines": "Total Headlines", + "@totalHeadlines": { + "description": "Label for the total headlines summary card on the dashboard" + }, + "totalCategories": "Total Categories", + "@totalCategories": { + "description": "Label for the total categories summary card on the dashboard" + }, + "totalSources": "Total Sources", + "@totalSources": { + "description": "Label for the total sources summary card on the dashboard" + }, + "loadingDashboard": "Loading Dashboard", + "@loadingDashboard": { + "description": "Headline for the dashboard loading state" + }, + "loadingDashboardSubheadline": "Fetching latest statistics...", + "@loadingDashboardSubheadline": { + "description": "Subheadline for the dashboard loading state" + }, + "dashboardLoadFailure": "Failed to load dashboard data.", + "@dashboardLoadFailure": { + "description": "Error message when dashboard data fails to load" + }, + "recentHeadlines": "Recent Headlines", + "@recentHeadlines": { + "description": "Title for the recent headlines card on the dashboard" + }, + "viewAll": "View All", + "@viewAll": { + "description": "Button text to view all items in a list" + }, + "noRecentHeadlines": "No recent headlines to display.", + "@noRecentHeadlines": { + "description": "Message shown when there are no recent headlines" + }, + "systemStatus": "System Status", + "@systemStatus": { + "description": "Title for the system status card on the dashboard" + }, + "quickActions": "Quick Actions", + "@quickActions": { + "description": "Title for the quick actions card on the dashboard" + }, + "createHeadlineAction": "Create Headline", + "@createHeadlineAction": { + "description": "Button text for the create headline quick action" + }, + "manageContentAction": "Manage Content", + "@manageContentAction": { + "description": "Button text for the manage content quick action" + }, + "appConfigAction": "App Configuration", + "@appConfigAction": { + "description": "Button text for the app configuration quick action" + }, + "appStatusActive": "Active", + "@appStatusActive": { + "description": "Text for the 'Active' app status" + }, + "appStatusMaintenance": "Maintenance", + "@appStatusMaintenance": { + "description": "Text for the 'Maintenance' app status" + }, + "appStatusDisabled": "Disabled", + "@appStatusDisabled": { + "description": "Text for the 'Disabled' app status" + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index e166c2b..85985da 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -241,7 +241,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: d64d8b8cf24f9b4bc5ad639b9c6b968807cf5d93 + resolved-ref: b3073b812b4d5216f0ce5658be1be52e193083bb url: "https://github.com/headlines-toolkit/ht-data-api.git" source: git version: "0.0.0" @@ -250,7 +250,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "2b9c82fc5d3aff78e1e6f51b844927508bc06e48" + resolved-ref: "0077622bbd5c8886a4d7bfdf65540e6965564ad1" url: "https://github.com/headlines-toolkit/ht-data-client.git" source: git version: "0.0.0" @@ -259,7 +259,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "3353842a1f1be1ab2563f2ba236daf49dd19a85e" + resolved-ref: "948d8a237c9465b8fd9d3ab78bcea10841dc9a90" url: "https://github.com/headlines-toolkit/ht-data-inmemory.git" source: git version: "0.0.0" @@ -268,7 +268,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "2f1b1e81ff91eeae636f822edbb9088a28fd4906" + resolved-ref: "434c7a02cc85b7243a9a2f1bd662557ee19c3479" url: "https://github.com/headlines-toolkit/ht-data-repository.git" source: git version: "0.0.0" @@ -304,7 +304,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "9e771623f5745113d346fd4bfdb1abccf7e75049" + resolved-ref: b3a339a2957b35a2bb7baf249e89ef9ca296eb3e url: "https://github.com/headlines-toolkit/ht-shared.git" source: git version: "0.0.0"