feat: Enhance review order summary with hours display and localization for hours suffix
This commit is contained in:
@@ -418,7 +418,8 @@
|
||||
"total": "Total",
|
||||
"estimated_total": "Estimated Total",
|
||||
"estimated_weekly_total": "Estimated Weekly Total",
|
||||
"post_order": "Post Order"
|
||||
"post_order": "Post Order",
|
||||
"hours_suffix": "hrs"
|
||||
},
|
||||
"rapid_draft": {
|
||||
"title": "Rapid Order",
|
||||
|
||||
@@ -418,7 +418,8 @@
|
||||
"total": "Total",
|
||||
"estimated_total": "Total Estimado",
|
||||
"estimated_weekly_total": "Total Semanal Estimado",
|
||||
"post_order": "Publicar Orden"
|
||||
"post_order": "Publicar Orden",
|
||||
"hours_suffix": "hrs"
|
||||
},
|
||||
"rapid_draft": {
|
||||
"title": "Orden R\u00e1pida",
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../blocs/one_time_order/one_time_order_bloc.dart';
|
||||
import '../blocs/one_time_order/one_time_order_event.dart';
|
||||
import '../blocs/one_time_order/one_time_order_state.dart';
|
||||
import '../models/review_order_arguments.dart';
|
||||
import '../utils/time_parsing_utils.dart';
|
||||
import '../widgets/review_order/review_order_positions_card.dart';
|
||||
|
||||
/// Page for creating a one-time staffing order.
|
||||
@@ -117,6 +118,9 @@ class OneTimeOrderPage extends StatelessWidget {
|
||||
roleName: state.roleNameById(p.role) ?? p.role,
|
||||
workerCount: p.count,
|
||||
costPerHour: state.roleCostById(p.role),
|
||||
hours: parseHoursFromTimes(p.startTime, p.endTime),
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
),
|
||||
).toList();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../blocs/permanent_order/permanent_order_event.dart';
|
||||
import '../blocs/permanent_order/permanent_order_state.dart';
|
||||
import '../models/review_order_arguments.dart';
|
||||
import '../utils/schedule_utils.dart';
|
||||
import '../utils/time_parsing_utils.dart';
|
||||
import '../widgets/review_order/review_order_positions_card.dart';
|
||||
|
||||
/// Page for creating a permanent staffing order.
|
||||
@@ -129,6 +130,9 @@ class PermanentOrderPage extends StatelessWidget {
|
||||
roleName: state.roleNameById(p.role) ?? p.role,
|
||||
workerCount: p.count,
|
||||
costPerHour: state.roleCostById(p.role),
|
||||
hours: parseHoursFromTimes(p.startTime, p.endTime),
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
),
|
||||
).toList();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../blocs/recurring_order/recurring_order_event.dart';
|
||||
import '../blocs/recurring_order/recurring_order_state.dart';
|
||||
import '../models/review_order_arguments.dart';
|
||||
import '../utils/schedule_utils.dart';
|
||||
import '../utils/time_parsing_utils.dart';
|
||||
import '../widgets/review_order/review_order_positions_card.dart';
|
||||
|
||||
/// Page for creating a recurring staffing order.
|
||||
@@ -138,6 +139,9 @@ class RecurringOrderPage extends StatelessWidget {
|
||||
roleName: state.roleNameById(p.role) ?? p.role,
|
||||
workerCount: p.count,
|
||||
costPerHour: state.roleCostById(p.role),
|
||||
hours: parseHoursFromTimes(p.startTime, p.endTime),
|
||||
startTime: p.startTime,
|
||||
endTime: p.endTime,
|
||||
),
|
||||
).toList();
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'review_order_info_row.dart';
|
||||
|
||||
/// Displays a summary of all positions with a divider and total row.
|
||||
///
|
||||
/// Each position shows the role name and "N workers . $X/hr".
|
||||
/// Each position is rendered as a two-line layout:
|
||||
/// - Line 1: role name (left) and worker count with cost/hr (right).
|
||||
/// - Line 2: time range and shift hours (right-aligned, muted style).
|
||||
///
|
||||
/// A divider separates the individual positions from the total.
|
||||
class ReviewOrderPositionsCard extends StatelessWidget {
|
||||
/// Creates a [ReviewOrderPositionsCard].
|
||||
const ReviewOrderPositionsCard({
|
||||
required this.positions,
|
||||
required this.totalWorkers,
|
||||
@@ -16,9 +19,16 @@ class ReviewOrderPositionsCard extends StatelessWidget {
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The list of position items to display.
|
||||
final List<ReviewPositionItem> positions;
|
||||
|
||||
/// The total number of workers across all positions.
|
||||
final int totalWorkers;
|
||||
|
||||
/// The combined cost per hour across all positions.
|
||||
final double totalCostPerHour;
|
||||
|
||||
/// Optional callback invoked when the user taps "Edit".
|
||||
final VoidCallback? onEdit;
|
||||
|
||||
@override
|
||||
@@ -50,16 +60,7 @@ class ReviewOrderPositionsCard extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
...positions.map(
|
||||
(ReviewPositionItem position) => Padding(
|
||||
padding: const EdgeInsets.only(top: UiConstants.space3),
|
||||
child: ReviewOrderInfoRow(
|
||||
label: position.roleName,
|
||||
value:
|
||||
'${position.workerCount} workers \u00B7 \$${position.costPerHour.toStringAsFixed(0)}/hr',
|
||||
),
|
||||
),
|
||||
),
|
||||
...positions.map(_buildPositionItem),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: UiConstants.space3),
|
||||
child: Container(
|
||||
@@ -74,11 +75,12 @@ class ReviewOrderPositionsCard extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_create_order.review.total,
|
||||
style: UiTypography.body3m,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
'$totalWorkers workers \u00B7 \$${totalCostPerHour.toStringAsFixed(0)}/hr',
|
||||
style: UiTypography.body3b.primary,
|
||||
'$totalWorkers workers \u00B7 '
|
||||
'\$${totalCostPerHour.toStringAsFixed(0)}/hr',
|
||||
style: UiTypography.body2b.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -87,17 +89,83 @@ class ReviewOrderPositionsCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a two-line widget for a single position.
|
||||
///
|
||||
/// Line 1 shows the role name on the left and worker count with cost on
|
||||
/// the right. Line 2 shows the time range and shift hours, right-aligned
|
||||
/// in a secondary/muted style.
|
||||
Widget _buildPositionItem(ReviewPositionItem position) {
|
||||
final String formattedHours = position.hours % 1 == 0
|
||||
? position.hours.toInt().toString()
|
||||
: position.hours.toStringAsFixed(1);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: UiConstants.space3),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Text(
|
||||
position.roleName,
|
||||
style: UiTypography.body2m.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${position.workerCount} workers \u00B7 '
|
||||
'\$${position.costPerHour.toStringAsFixed(0)}/hr',
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'${position.startTime} - ${position.endTime} \u00B7 '
|
||||
'$formattedHours '
|
||||
'${t.client_create_order.review.hours_suffix}',
|
||||
style: UiTypography.body3r.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A single position item for the positions card.
|
||||
///
|
||||
/// Contains the role name, worker count, shift hours, hourly cost,
|
||||
/// and the start/end times for one position in the review summary.
|
||||
class ReviewPositionItem {
|
||||
/// Creates a [ReviewPositionItem].
|
||||
const ReviewPositionItem({
|
||||
required this.roleName,
|
||||
required this.workerCount,
|
||||
required this.costPerHour,
|
||||
required this.hours,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
});
|
||||
|
||||
/// The display name of the role for this position.
|
||||
final String roleName;
|
||||
|
||||
/// The number of workers requested for this position.
|
||||
final int workerCount;
|
||||
|
||||
/// The cost per hour for this role.
|
||||
final double costPerHour;
|
||||
|
||||
/// The number of shift hours (derived from start/end time).
|
||||
final double hours;
|
||||
|
||||
/// The formatted start time of the shift (e.g. "08:00 AM").
|
||||
final String startTime;
|
||||
|
||||
/// The formatted end time of the shift (e.g. "04:00 PM").
|
||||
final String endTime;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user