From 3b7715a382f19e712e075e1e3cbcd4ad9e0efb9f Mon Sep 17 00:00:00 2001 From: Suriya Date: Thu, 19 Feb 2026 13:09:44 +0530 Subject: [PATCH] localization reports page --- .../lib/src/l10n/en.i18n.json | 65 +++++++------------ .../pages/coverage_report_page.dart | 24 +++---- .../pages/daily_ops_report_page.dart | 4 +- .../pages/forecast_report_page.dart | 12 ++-- .../pages/no_show_report_page.dart | 42 ++++++------ .../pages/performance_report_page.dart | 38 +++++------ .../presentation/pages/spend_report_page.dart | 22 +++---- 7 files changed, 96 insertions(+), 111 deletions(-) diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index c3810474..274a2416 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -1254,6 +1254,7 @@ } }, "all_shifts_title": "ALL SHIFTS", + "no_shifts_today": "No shifts scheduled for today", "shift_item": { "time": "Time", "workers": "Workers", @@ -1289,6 +1290,7 @@ "sun": "Sun" }, "spend_by_industry": "Spend by Industry", + "no_industry_data": "No industry data available", "industries": { "hospitality": "Hospitality", "events": "Events", @@ -1319,41 +1321,16 @@ }, "forecast_report": { "title": "Forecast Report", - "subtitle": "Next 4 weeks projection", - "summary": { - "four_week": "4-Week Forecast", - "avg_weekly": "Avg Weekly", - "total_shifts": "Total Shifts", - "total_hours": "Total Hours", - "total_projected": "Total projected", - "per_week": "Per week", - "scheduled": "Scheduled", - "worker_hours": "Worker hours" + "subtitle": "Projected spend & staffing", + "metrics": { + "projected_spend": "Projected Spend", + "workers_needed": "Workers Needed" }, - "spending_forecast": "Spending Forecast", - "weekly_breakdown": "WEEKLY BREAKDOWN", - "breakdown_headings": { - "shifts": "Shifts", - "hours": "Hours", - "avg_shift": "Avg/Shift" - }, - "insights": { - "title": "Forecast Insights", - "insight_1": { - "prefix": "Demand is expected to spike by ", - "highlight": "25%", - "suffix": " in week 3" - }, - "insight_2": { - "prefix": "Projected spend for next month is ", - "highlight": "USD 68.4k", - "suffix": "" - }, - "insight_3": { - "prefix": "Consider increasing budget for ", - "highlight": "Holiday Season", - "suffix": " coverage" - } + "chart_title": "Spending Forecast", + "daily_projections": "DAILY PROJECTIONS", + "empty_state": "No projections available", + "shift_item": { + "workers_needed": "$count workers needed" }, "placeholders": { "export_message": "Exporting Forecast Report (Placeholder)" @@ -1364,7 +1341,9 @@ "subtitle": "Key metrics & benchmarks", "overall_score": { "title": "Overall Performance Score", - "label": "Excellent" + "excellent": "Excellent", + "good": "Good", + "needs_work": "Needs Work" }, "kpis_title": "KEY PERFORMANCE INDICATORS", "kpis": { @@ -1373,8 +1352,11 @@ "on_time_rate": "On-Time Rate", "avg_fill_time": "Avg Fill Time", "target_prefix": "Target: ", + "target_hours": "$hours hrs", + "target_percent": "$percent%", "met": "✓ Met", - "close": "↗ Close" + "close": "→ Close", + "miss": "✗ Miss" }, "additional_metrics_title": "ADDITIONAL METRICS", "additional_metrics": { @@ -1425,13 +1407,13 @@ "title": "Reliability Insights", "insight_1": { "prefix": "Your no-show rate of ", - "highlight": "1.2%", - "suffix": " is below industry average" + "highlight": "$rate%", + "suffix": " is $comparison industry average" }, "insight_2": { "prefix": "", - "highlight": "1 worker", - "suffix": " has multiple incidents this month" + "highlight": "$count worker(s)", + "suffix": " have multiple incidents this month" }, "insight_3": { "prefix": "Consider implementing ", @@ -1439,6 +1421,7 @@ "suffix": " 24hrs before shifts" } }, + "empty_state": "No workers flagged for no-shows", "placeholders": { "export_message": "Exporting No-Show Report (Placeholder)" } @@ -1452,9 +1435,11 @@ "needs_help": "Needs Help" }, "next_7_days": "NEXT 7 DAYS", + "empty_state": "No shifts scheduled", "shift_item": { "confirmed_workers": "$confirmed/$needed workers confirmed", "spots_remaining": "$count spots remaining", + "one_spot_remaining": "1 spot remaining", "fully_staffed": "Fully staffed" }, "insights": { diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/coverage_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/coverage_report_page.dart index 25b68528..24a0bef4 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/coverage_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/coverage_report_page.dart @@ -175,21 +175,21 @@ class _CoverageReportPageState extends State { children: [ _CoverageStatCard( icon: UiIcons.trendingUp, - label: 'Avg Coverage', + label: context.t.client_reports.coverage_report.metrics.avg_coverage, value: '${report.overallCoverage.toStringAsFixed(0)}%', iconColor: UiColors.primary, ), const SizedBox(width: 12), _CoverageStatCard( icon: UiIcons.checkCircle, - label: 'Full', + label: context.t.client_reports.coverage_report.metrics.full, value: fullDays.toString(), iconColor: UiColors.success, ), const SizedBox(width: 12), _CoverageStatCard( icon: UiIcons.warning, - label: 'Needs Help', + label: context.t.client_reports.coverage_report.metrics.needs_help, value: needsHelpDays.toString(), iconColor: UiColors.error, ), @@ -209,8 +209,8 @@ class _CoverageReportPageState extends State { const SizedBox(height: 32), // Section label - const Text( - 'NEXT 7 DAYS', + Text( + context.t.client_reports.coverage_report.next_7_days, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -224,9 +224,9 @@ class _CoverageReportPageState extends State { Container( padding: const EdgeInsets.all(40), alignment: Alignment.center, - child: const Text( - 'No shifts scheduled', - style: TextStyle( + child: Text( + context.t.client_reports.coverage_report.empty_state, + style: const TextStyle( color: UiColors.textSecondary, ), ), @@ -398,7 +398,7 @@ class _DayCoverageCard extends StatelessWidget { ), const SizedBox(height: 2), Text( - '$filled/$needed workers confirmed', + context.t.client_reports.coverage_report.shift_item.confirmed_workers(confirmed: filled.toString(), needed: needed.toString()), style: const TextStyle( fontSize: 12, color: UiColors.textSecondary, @@ -442,8 +442,10 @@ class _DayCoverageCard extends StatelessWidget { alignment: Alignment.centerRight, child: Text( isFullyStaffed - ? 'Fully staffed' - : '$spotsRemaining spot${spotsRemaining != 1 ? 's' : ''} remaining', + ? context.t.client_reports.coverage_report.shift_item.fully_staffed + : spotsRemaining == 1 + ? context.t.client_reports.coverage_report.shift_item.one_spot_remaining + : context.t.client_reports.coverage_report.shift_item.spots_remaining(count: spotsRemaining.toString()), style: TextStyle( fontSize: 11, color: isFullyStaffed diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/daily_ops_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/daily_ops_report_page.dart index 323cbcf6..a0b3c512 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/daily_ops_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/daily_ops_report_page.dart @@ -335,7 +335,7 @@ class _DailyOpsReportPageState extends State { const Padding( padding: EdgeInsets.symmetric(vertical: 40), child: Center( - child: Text('No shifts scheduled for today'), + child: Text(context.t.client_reports.daily_ops_report.no_shifts_today), ), ) else @@ -579,7 +579,7 @@ class _ShiftListItem extends StatelessWidget { _infoItem( context, UiIcons.trendingUp, - 'Rate', + context.t.client_reports.daily_ops_report.shift_item.rate, rate), ], ), diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/forecast_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/forecast_report_page.dart index e6059237..b7e11efc 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/forecast_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/forecast_report_page.dart @@ -120,7 +120,7 @@ class _ForecastReportPageState extends State { children: [ Expanded( child: _ForecastSummaryCard( - label: 'Projected Spend', + label: context.t.client_reports.forecast_report.metrics.projected_spend, value: NumberFormat.currency(symbol: r'$') .format(report.projectedSpend), icon: UiIcons.dollar, @@ -130,7 +130,7 @@ class _ForecastReportPageState extends State { const SizedBox(width: 12), Expanded( child: _ForecastSummaryCard( - label: 'Workers Needed', + label: context.t.client_reports.forecast_report.metrics.workers_needed, value: report.projectedWorkers.toString(), icon: UiIcons.users, color: UiColors.primary, @@ -158,7 +158,7 @@ class _ForecastReportPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Spending Forecast', + context.t.client_reports.forecast_report.chart_title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -178,7 +178,7 @@ class _ForecastReportPageState extends State { // Daily List Text( - 'DAILY PROJECTIONS', + context.t.client_reports.forecast_report.daily_projections, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, @@ -188,7 +188,7 @@ class _ForecastReportPageState extends State { ), const SizedBox(height: 16), if (report.chartData.isEmpty) - const Center(child: Text('No projections available')) + Center(child: Text(context.t.client_reports.forecast_report.empty_state)) else ...report.chartData.map((point) => _ForecastListItem( date: DateFormat('EEE, MMM dd').format(point.date), @@ -348,7 +348,7 @@ class _ForecastListItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(date, style: const TextStyle(fontWeight: FontWeight.bold)), - Text('$workers workers needed', style: const TextStyle(fontSize: 11, color: UiColors.textSecondary)), + Text(context.t.client_reports.forecast_report.shift_item.workers_needed(count: workers), style: const TextStyle(fontSize: 11, color: UiColors.textSecondary)), ], ), Text(cost, style: const TextStyle(fontWeight: FontWeight.bold, color: UiColors.primary)), diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/no_show_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/no_show_report_page.dart index 100f398e..5c94d928 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/no_show_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/no_show_report_page.dart @@ -158,7 +158,7 @@ class _NoShowReportPageState extends State { child: _SummaryChip( icon: UiIcons.warning, iconColor: UiColors.error, - label: 'No-Shows', + label: context.t.client_reports.no_show_report.metrics.no_shows, value: report.totalNoShows.toString(), ), ), @@ -167,7 +167,7 @@ class _NoShowReportPageState extends State { child: _SummaryChip( icon: UiIcons.trendingUp, iconColor: UiColors.textWarning, - label: 'Rate', + label: context.t.client_reports.no_show_report.metrics.rate, value: '${report.noShowRate.toStringAsFixed(1)}%', ), @@ -177,7 +177,7 @@ class _NoShowReportPageState extends State { child: _SummaryChip( icon: UiIcons.user, iconColor: UiColors.primary, - label: 'Workers', + label: context.t.client_reports.no_show_report.metrics.workers, value: uniqueWorkers.toString(), ), ), @@ -204,9 +204,9 @@ class _NoShowReportPageState extends State { Container( padding: const EdgeInsets.all(40), alignment: Alignment.center, - child: const Text( - 'No workers flagged for no-shows', - style: TextStyle( + child: Text( + context.t.client_reports.no_show_report.empty_state, + style: const TextStyle( color: UiColors.textSecondary, ), ), @@ -232,9 +232,9 @@ class _NoShowReportPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - '💡 Reliability Insights', - style: TextStyle( + Text( + '💡 ${context.t.client_reports.no_show_report.insights.title}', + style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: UiColors.textPrimary, @@ -243,21 +243,19 @@ class _NoShowReportPageState extends State { const SizedBox(height: 12), _InsightLine( text: - '· Your no-show rate of ${report.noShowRate.toStringAsFixed(1)}% is ' - '${report.noShowRate < 5 ? 'below' : 'above'} industry average', + '· ${context.t.client_reports.no_show_report.insights.insight_1.prefix}${context.t.client_reports.no_show_report.insights.insight_1.highlight(rate: report.noShowRate.toStringAsFixed(1))}${context.t.client_reports.no_show_report.insights.insight_1.suffix(comparison: report.noShowRate < 5 ? 'below' : 'above')}', ), if (report.flaggedWorkers.any( (w) => w.noShowCount > 1, )) _InsightLine( text: - '· ${report.flaggedWorkers.where((w) => w.noShowCount > 1).length} ' - 'worker(s) have multiple incidents this month', + '· ${context.t.client_reports.no_show_report.insights.insight_2.highlight(count: report.flaggedWorkers.where((w) => w.noShowCount > 1).length.toString())} ${context.t.client_reports.no_show_report.insights.insight_2.suffix}', bold: true, ), - const _InsightLine( + _InsightLine( text: - '· Consider implementing confirmation reminders 24hrs before shifts', + '· ${context.t.client_reports.no_show_report.insights.insight_3.prefix}${context.t.client_reports.no_show_report.insights.insight_3.highlight}${context.t.client_reports.no_show_report.insights.insight_3.suffix}', bold: true, ), ], @@ -352,9 +350,9 @@ class _WorkerCard extends StatelessWidget { const _WorkerCard({required this.worker}); String _riskLabel(int count) { - if (count >= 3) return 'High Risk'; - if (count == 2) return 'Medium Risk'; - return 'Low Risk'; + if (count >= 3) return context.t.client_reports.no_show_report.risks.high; + if (count == 2) return context.t.client_reports.no_show_report.risks.medium; + return context.t.client_reports.no_show_report.risks.low; } Color _riskColor(int count) { @@ -421,7 +419,7 @@ class _WorkerCard extends StatelessWidget { ), ), Text( - '${worker.noShowCount} no-show${worker.noShowCount > 1 ? 's' : ''}', + context.t.client_reports.no_show_report.no_show_count(count: worker.noShowCount.toString()), style: const TextStyle( fontSize: 12, color: UiColors.textSecondary, @@ -458,9 +456,9 @@ class _WorkerCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'Latest incident', - style: TextStyle( + Text( + context.t.client_reports.no_show_report.latest_incident, + style: const TextStyle( fontSize: 11, color: UiColors.textSecondary, ), diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/performance_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/performance_report_page.dart index 837053fd..d1455b42 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/performance_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/performance_report_page.dart @@ -50,10 +50,10 @@ class _PerformanceReportPageState extends State { .clamp(0.0, 100.0); final scoreLabel = overallScore >= 90 - ? 'Excellent' + ? context.t.client_reports.performance_report.overall_score.excellent : overallScore >= 75 - ? 'Good' - : 'Needs Work'; + ? context.t.client_reports.performance_report.overall_score.good + : context.t.client_reports.performance_report.overall_score.needs_work; final scoreLabelColor = overallScore >= 90 ? UiColors.success : overallScore >= 75 @@ -70,8 +70,8 @@ class _PerformanceReportPageState extends State { _KpiData( icon: UiIcons.users, iconColor: UiColors.primary, - label: 'Fill Rate', - target: 'Target: 95%', + label: context.t.client_reports.performance_report.kpis.fill_rate, + target: context.t.client_reports.performance_report.kpis.target_percent(percent: '95'), value: report.fillRate, displayValue: '${report.fillRate.toStringAsFixed(0)}%', barColor: UiColors.primary, @@ -81,8 +81,8 @@ class _PerformanceReportPageState extends State { _KpiData( icon: UiIcons.checkCircle, iconColor: UiColors.success, - label: 'Completion Rate', - target: 'Target: 98%', + label: context.t.client_reports.performance_report.kpis.completion_rate, + target: context.t.client_reports.performance_report.kpis.target_percent(percent: '98'), value: report.completionRate, displayValue: '${report.completionRate.toStringAsFixed(0)}%', barColor: UiColors.success, @@ -92,8 +92,8 @@ class _PerformanceReportPageState extends State { _KpiData( icon: UiIcons.clock, iconColor: const Color(0xFF9B59B6), - label: 'On-Time Rate', - target: 'Target: 97%', + label: context.t.client_reports.performance_report.kpis.on_time_rate, + target: context.t.client_reports.performance_report.kpis.target_percent(percent: '97'), value: report.onTimeRate, displayValue: '${report.onTimeRate.toStringAsFixed(0)}%', barColor: const Color(0xFF9B59B6), @@ -103,8 +103,8 @@ class _PerformanceReportPageState extends State { _KpiData( icon: UiIcons.trendingUp, iconColor: const Color(0xFFF39C12), - label: 'Avg Fill Time', - target: 'Target: 3 hrs', + label: context.t.client_reports.performance_report.kpis.avg_fill_time, + target: context.t.client_reports.performance_report.kpis.target_hours(hours: '3'), // invert: lower is better — show as % of target met value: report.avgFillTimeHours == 0 ? 100 @@ -256,8 +256,8 @@ class _PerformanceReportPageState extends State { color: UiColors.primary, ), const SizedBox(height: 12), - const Text( - 'Overall Performance Score', + Text( + context.t.client_reports.performance_report.overall_score.title, style: TextStyle( fontSize: 13, color: UiColors.textSecondary, @@ -313,9 +313,9 @@ class _PerformanceReportPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'KEY PERFORMANCE INDICATORS', - style: TextStyle( + Text( + context.t.client_reports.performance_report.kpis_title, + style: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: UiColors.textSecondary, @@ -381,10 +381,10 @@ class _KpiRow extends StatelessWidget { @override Widget build(BuildContext context) { final badgeText = kpi.met - ? '✓ Met' + ? context.t.client_reports.performance_report.kpis.met : kpi.close - ? '→ Close' - : '✗ Miss'; + ? context.t.client_reports.performance_report.kpis.close + : context.t.client_reports.performance_report.kpis.miss; final badgeColor = kpi.met ? UiColors.success : kpi.close diff --git a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/spend_report_page.dart b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/spend_report_page.dart index a09aa76c..77798c80 100644 --- a/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/spend_report_page.dart +++ b/apps/mobile/packages/features/client/reports/lib/src/presentation/pages/spend_report_page.dart @@ -220,9 +220,9 @@ class _SpendReportPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Daily Spend Trend', - style: TextStyle( + Text( + context.t.client_reports.spend_report.chart_title, + style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: UiColors.textPrimary, @@ -475,9 +475,9 @@ class _SpendByIndustryCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Spend by Industry', - style: TextStyle( + Text( + context.t.client_reports.spend_report.spend_by_industry, + style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: UiColors.textPrimary, @@ -485,12 +485,12 @@ class _SpendByIndustryCard extends StatelessWidget { ), const SizedBox(height: 24), if (industries.isEmpty) - const Center( + Center( child: Padding( - padding: EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16.0), child: Text( - 'No industry data available', - style: TextStyle(color: UiColors.textSecondary), + context.t.client_reports.spend_report.no_industry_data, + style: const TextStyle(color: UiColors.textSecondary), ), ), ) @@ -533,7 +533,7 @@ class _SpendByIndustryCard extends StatelessWidget { ), const SizedBox(height: 6), Text( - '${ind.percentage.toStringAsFixed(1)}% of total', + context.t.client_reports.spend_report.percent_total(percent: ind.percentage.toStringAsFixed(1)), style: const TextStyle( fontSize: 10, color: UiColors.textDescription,