feat(shifts): enhance TodayShift and shift card components with client details and pay calculations
This commit is contained in:
@@ -21,11 +21,20 @@ class RecommendedShiftCard extends StatelessWidget {
|
||||
return DateFormat('h:mma').format(time).toLowerCase();
|
||||
}
|
||||
|
||||
/// Computes the shift duration in whole hours.
|
||||
double _durationHours() {
|
||||
final int minutes = shift.endTime.difference(shift.startTime).inMinutes;
|
||||
double hours = minutes / 60;
|
||||
if (hours < 0) hours += 24;
|
||||
return hours.roundToDouble();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dynamic recI18n = t.staff.home.recommended_card;
|
||||
final Size size = MediaQuery.sizeOf(context);
|
||||
final double hourlyRate = shift.hourlyRateCents / 100;
|
||||
final double durationHours = _durationHours();
|
||||
final double estimatedTotal = shift.hourlyRate * durationHours;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Modular.to.toShiftDetailsById(shift.shiftId),
|
||||
@@ -75,8 +84,8 @@ class RecommendedShiftCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'\$${hourlyRate.toStringAsFixed(0)}/h',
|
||||
style: UiTypography.headline4b,
|
||||
'\$${estimatedTotal.toStringAsFixed(0)}',
|
||||
style: UiTypography.title1m.textPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -89,8 +98,8 @@ class RecommendedShiftCard extends StatelessWidget {
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
Text(
|
||||
'\$${hourlyRate.toStringAsFixed(0)}/hr',
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
'\$${shift.hourlyRate.toInt()}/hr \u00b7 ${durationHours.toInt()}h',
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -66,8 +66,31 @@ class _TodayShiftCard extends StatelessWidget {
|
||||
return DateFormat('h:mma').format(time).toLowerCase();
|
||||
}
|
||||
|
||||
/// Computes the shift duration in whole hours.
|
||||
double _durationHours() {
|
||||
final int minutes = shift.endTime.difference(shift.startTime).inMinutes;
|
||||
double hours = minutes / 60;
|
||||
if (hours < 0) hours += 24;
|
||||
return hours.roundToDouble();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool hasRate = shift.hourlyRate > 0;
|
||||
final String title = shift.clientName.isNotEmpty
|
||||
? shift.clientName
|
||||
: shift.roleName;
|
||||
final String? subtitle = shift.clientName.isNotEmpty
|
||||
? shift.roleName
|
||||
: null;
|
||||
final double durationHours = _durationHours();
|
||||
final double estimatedTotal = shift.totalRate > 0
|
||||
? shift.totalRate
|
||||
: shift.hourlyRate * durationHours;
|
||||
final String displayLocation = shift.locationAddress?.isNotEmpty == true
|
||||
? shift.locationAddress!
|
||||
: shift.location;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Modular.to.toShiftDetailsById(shift.shiftId),
|
||||
child: Container(
|
||||
@@ -99,15 +122,65 @@ class _TodayShiftCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
shift.roleName,
|
||||
style: UiTypography.body1m.textPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Text(
|
||||
title,
|
||||
style: UiTypography.body1m.textPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (hasRate)
|
||||
Text(
|
||||
'\$${estimatedTotal.toStringAsFixed(0)}',
|
||||
style: UiTypography.title1m.textPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (hasRate) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
'\$${shift.hourlyRate.toInt()}/hr \u00b7 ${durationHours.toInt()}h',
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
'${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)} \u2022 ${shift.location}',
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
UiIcons.clock,
|
||||
size: UiConstants.space3,
|
||||
color: UiColors.mutedForeground,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
'${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}',
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Icon(
|
||||
UiIcons.mapPin,
|
||||
size: UiConstants.space3,
|
||||
color: UiColors.mutedForeground,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Expanded(
|
||||
child: Text(
|
||||
displayLocation,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -57,9 +57,27 @@ class _TomorrowShiftCard extends StatelessWidget {
|
||||
return DateFormat('h:mma').format(time).toLowerCase();
|
||||
}
|
||||
|
||||
/// Computes the shift duration in whole hours.
|
||||
double _durationHours() {
|
||||
final int minutes = shift.endTime.difference(shift.startTime).inMinutes;
|
||||
double hours = minutes / 60;
|
||||
if (hours < 0) hours += 24;
|
||||
return hours.roundToDouble();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double hourlyRate = shift.hourlyRateCents / 100;
|
||||
final bool hasRate = shift.hourlyRate > 0;
|
||||
final String title = shift.clientName.isNotEmpty
|
||||
? shift.clientName
|
||||
: shift.roleName;
|
||||
final String? subtitle = shift.clientName.isNotEmpty
|
||||
? shift.roleName
|
||||
: null;
|
||||
final double durationHours = _durationHours();
|
||||
final double estimatedTotal = shift.totalRate > 0
|
||||
? shift.totalRate
|
||||
: shift.hourlyRate * durationHours;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Modular.to.toShiftDetailsById(shift.shiftId),
|
||||
@@ -97,31 +115,61 @@ class _TomorrowShiftCard extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Text(
|
||||
shift.roleName,
|
||||
title,
|
||||
style: UiTypography.body1m.textPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
text:
|
||||
'\$${hourlyRate % 1 == 0 ? hourlyRate.toInt() : hourlyRate.toStringAsFixed(2)}',
|
||||
style: UiTypography.body1b.textPrimary,
|
||||
children: <InlineSpan>[
|
||||
TextSpan(
|
||||
text: '/h',
|
||||
style: UiTypography.body3r,
|
||||
),
|
||||
],
|
||||
if (hasRate)
|
||||
Text(
|
||||
'\$${estimatedTotal.toStringAsFixed(0)}',
|
||||
style: UiTypography.title1m.textPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (hasRate) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
'\$${shift.hourlyRate.toInt()}/hr \u00b7 ${durationHours.toInt()}h',
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
UiIcons.clock,
|
||||
size: UiConstants.space3,
|
||||
color: UiColors.mutedForeground,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
'${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}',
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Icon(
|
||||
UiIcons.mapPin,
|
||||
size: UiConstants.space3,
|
||||
color: UiColors.mutedForeground,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Expanded(
|
||||
child: Text(
|
||||
shift.location,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
'${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)} \u2022 ${shift.location}',
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user