localization reports page
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user