localization reports page

This commit is contained in:
2026-02-19 13:09:44 +05:30
parent c4610003b4
commit 3b7715a382
7 changed files with 96 additions and 111 deletions

View File

@@ -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": {

View File

@@ -175,21 +175,21 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
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<CoverageReportPage> {
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<CoverageReportPage> {
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

View File

@@ -335,7 +335,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
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),
],
),

View File

@@ -120,7 +120,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
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<ForecastReportPage> {
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<ForecastReportPage> {
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<ForecastReportPage> {
// 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<ForecastReportPage> {
),
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)),

View File

@@ -158,7 +158,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
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<NoShowReportPage> {
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<NoShowReportPage> {
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<NoShowReportPage> {
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<NoShowReportPage> {
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<NoShowReportPage> {
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,
),

View File

@@ -50,10 +50,10 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
.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<PerformanceReportPage> {
_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<PerformanceReportPage> {
_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<PerformanceReportPage> {
_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<PerformanceReportPage> {
_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<PerformanceReportPage> {
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<PerformanceReportPage> {
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

View File

@@ -220,9 +220,9 @@ class _SpendReportPageState extends State<SpendReportPage> {
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,