From 40fa4ebdfa39a8b61245d0ef28bee4094ea84323 Mon Sep 17 00:00:00 2001 From: Suriya Date: Mon, 16 Feb 2026 20:28:43 +0530 Subject: [PATCH] Refactor: Move detailed shift UI from card to ShiftDetailsPage --- .../pages/shift_details_page.dart | 214 +++-- .../presentation/widgets/my_shift_card.dart | 811 +++++------------- 2 files changed, 339 insertions(+), 686 deletions(-) diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart index 6c83272b..48cca943 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart @@ -1,5 +1,5 @@ import 'package:core_localization/core_localization.dart'; -import 'package:design_system/design_system.dart'; // Re-added for UiIcons/Colors as they are used in expanded logic +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; @@ -10,6 +10,7 @@ import 'package:krow_domain/krow_domain.dart'; import '../blocs/shift_details/shift_details_bloc.dart'; import '../blocs/shift_details/shift_details_event.dart'; import '../blocs/shift_details/shift_details_state.dart'; +import '../widgets/shift_location_map.dart'; class ShiftDetailsPage extends StatefulWidget { final String shiftId; @@ -65,10 +66,10 @@ class _ShiftDetailsPageState extends State { Widget _buildStatCard(IconData icon, String value, String label) { return Container( - padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space3), decoration: BoxDecoration( color: UiColors.background, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), border: Border.all(color: UiColors.border), ), child: Column( @@ -80,12 +81,12 @@ class _ShiftDetailsPageState extends State { color: UiColors.white, shape: BoxShape.circle, ), - child: Icon(icon, size: 20, color: UiColors.iconSecondary), + child: Icon(icon, size: 20, color: UiColors.textSecondary), ), const SizedBox(height: UiConstants.space2), Text( value, - style: UiTypography.title1m.textPrimary, + style: UiTypography.title1m.copyWith(fontWeight: FontWeight.w700).textPrimary, ), Text( label, @@ -98,21 +99,22 @@ class _ShiftDetailsPageState extends State { Widget _buildTimeBox(String label, String time) { return Container( - padding: const EdgeInsets.all(UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space3), decoration: BoxDecoration( color: UiColors.background, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), child: Column( children: [ Text( label, - style: UiTypography.titleUppercase4b.textSecondary, + style: UiTypography.footnote2b.copyWith( + color: UiColors.textSecondary, letterSpacing: 0.5), ), const SizedBox(height: UiConstants.space1), Text( _formatTime(time), - style: UiTypography.headline2m.textPrimary, + style: UiTypography.title1m.copyWith(fontWeight: FontWeight.w700).textPrimary, ), ], ), @@ -267,45 +269,49 @@ class _ShiftDetailsPageState extends State { ), const SizedBox(height: UiConstants.space6), - // Worker Capacity / Open Slots - if ((displayShift.requiredSlots ?? 0) > 0) - Container( - padding: const EdgeInsets.all(UiConstants.space4), - decoration: BoxDecoration( - color: UiColors.success.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + // Stats Row (New) + Row( + children: [ + Expanded( + child: _buildStatCard( + UiIcons.dollar, + "\$${estimatedTotal.toStringAsFixed(0)}", + "Total", + ), ), - child: Row( - children: [ - const Icon( - UiIcons.users, - size: 16, - color: UiColors.success, - ), - const SizedBox(width: UiConstants.space2), - Text( - i18n.slots_remaining(count: openSlots), - style: UiTypography.footnote1m.textSuccess, - ), - ], + const SizedBox(width: UiConstants.space4), + Expanded( + child: _buildStatCard( + UiIcons.dollar, + "\$${displayShift.hourlyRate.toStringAsFixed(0)}", + "Hourly Rate", + ), ), - ), - + const SizedBox(width: UiConstants.space4), + Expanded( + child: _buildStatCard( + UiIcons.clock, + "${duration.toStringAsFixed(1)}", + "Hours", + ), + ), + ], + ), const SizedBox(height: UiConstants.space6), - // Time Section + // Time Section (New) Row( children: [ Expanded( child: _buildTimeBox( - i18n.start_time, + "CLOCK IN TIME", displayShift.startTime, ), ), const SizedBox(width: UiConstants.space4), Expanded( child: _buildTimeBox( - i18n.end_time, + "CLOCK OUT TIME", displayShift.endTime, ), ), @@ -313,97 +319,79 @@ class _ShiftDetailsPageState extends State { ), const SizedBox(height: UiConstants.space6), - // Quick Info Grid - Row( - children: [ - Expanded( - child: _buildStatCard( - UiIcons.dollar, - "\$${displayShift.hourlyRate.toStringAsFixed(0)}/hr", - i18n.base_rate, - ), - ), - const SizedBox(width: UiConstants.space4), - Expanded( - child: _buildStatCard( - UiIcons.clock, - i18n.hours_label(count: duration.toInt()), - i18n.duration, - ), - ), - const SizedBox(width: UiConstants.space4), - Expanded( - child: _buildStatCard( - UiIcons.wallet, - "\$${estimatedTotal.toStringAsFixed(0)}", - i18n.est_total, - ), - ), - ], - ), - const SizedBox(height: UiConstants.space8), - // Location Section + // Location Section (New with Map) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - i18n.location, + "LOCATION", style: UiTypography.titleUppercase4b.textSecondary, ), const SizedBox(height: UiConstants.space3), - Container( - padding: const EdgeInsets.all(UiConstants.space4), - decoration: BoxDecoration( - color: UiColors.white, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), - border: Border.all(color: UiColors.border), - ), - child: Column( - children: [ - Row( - children: [ - const Icon( - UiIcons.mapPin, - color: UiColors.primary, - size: 20, - ), - const SizedBox(width: UiConstants.space3), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - displayShift.location, - style: UiTypography.body2b.textPrimary, - ), - Text( - displayShift.locationAddress, - style: UiTypography.body3r.textSecondary, - ), - ], + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + displayShift.location.isEmpty + ? "TBD" + : displayShift.location, + style: UiTypography.title1m.textPrimary, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: UiConstants.space3), + OutlinedButton.icon( + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar( + SnackBar( + content: Text( + displayShift!.locationAddress.isNotEmpty + ? displayShift!.locationAddress + : displayShift!.location, + ), + duration: const Duration( + seconds: 3, ), ), - ], + ); + }, + icon: const Icon( + UiIcons.navigation, + size: UiConstants.iconXs, ), - const SizedBox(height: UiConstants.space4), - const Divider(), - const SizedBox(height: UiConstants.space2), - TextButton.icon( - onPressed: () {}, - icon: const Icon( - UiIcons.arrowRight, - size: 16, - ), - label: Text(i18n.open_in_maps), - style: TextButton.styleFrom( - foregroundColor: UiColors.primary, - padding: EdgeInsets.zero, - ), + label: const Text( + "Get direction", ), - ], - ), + style: OutlinedButton.styleFrom( + foregroundColor: + UiColors.textPrimary, + side: const BorderSide( + color: UiColors.border, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + UiConstants.radiusBase, + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space3, + vertical: 0, + ), + minimumSize: const Size(0, 32), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space3), + ShiftLocationMap( + shift: displayShift, + height: 160, + borderRadius: UiConstants.radiusBase, ), ], ), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart index bbf5eb35..86352524 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart @@ -4,7 +4,6 @@ import 'package:intl/intl.dart'; import 'package:krow_domain/krow_domain.dart'; import 'package:design_system/design_system.dart'; import 'package:core_localization/core_localization.dart'; -import 'shift_location_map.dart'; import 'package:krow_core/core.dart'; // For modular navigation class MyShiftCard extends StatefulWidget { @@ -27,8 +26,7 @@ class MyShiftCard extends StatefulWidget { State createState() => _MyShiftCardState(); } -class _MyShiftCardState extends State with TickerProviderStateMixin { - bool _isExpanded = false; +class _MyShiftCardState extends State { String _formatTime(String time) { if (time.isEmpty) return ''; @@ -104,7 +102,6 @@ class _MyShiftCardState extends State with TickerProviderStateMixin IconData? statusIcon; // Fallback localization if keys missing - // Assuming t.staff_shifts.status.* exists as per previous file content try { if (status == 'confirmed') { statusText = t.staff_shifts.status.confirmed; @@ -137,9 +134,13 @@ class _MyShiftCardState extends State with TickerProviderStateMixin } return GestureDetector( - onTap: () => setState(() => _isExpanded = !_isExpanded), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + onTap: () { + Modular.to.pushNamed( + StaffPaths.shiftDetails(widget.shift.id), + arguments: widget.shift, + ); + }, + child: Container( margin: const EdgeInsets.only(bottom: UiConstants.space3), decoration: BoxDecoration( color: UiColors.white, @@ -153,582 +154,246 @@ class _MyShiftCardState extends State with TickerProviderStateMixin ), ], ), - child: Column( - children: [ - // Collapsed Content - Padding( - padding: const EdgeInsets.all(UiConstants.space4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Status Badge - if (statusText.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: UiConstants.space2), - child: Row( - children: [ - if (statusIcon != null) - Padding( - padding: const EdgeInsets.only(right: UiConstants.space2), - child: Icon( - statusIcon, - size: UiConstants.iconXs, - color: statusColor, - ), - ) - else - Container( - width: 8, - height: 8, - margin: const EdgeInsets.only(right: UiConstants.space2), - decoration: BoxDecoration( - color: statusBg, - shape: BoxShape.circle, - ), - ), - Text( - statusText, - style: UiTypography.footnote2b.copyWith( - color: statusColor, - letterSpacing: 0.5, - ), - ), - // Shift Type Badge for available/pending shifts - if (status == 'open' || status == 'pending') ...[ - const SizedBox(width: UiConstants.space2), - Container( - padding: const EdgeInsets.symmetric( - horizontal: UiConstants.space2, - vertical: 2, - ), - decoration: BoxDecoration( - color: UiColors.primary.withValues(alpha: 0.1), - borderRadius: UiConstants.radiusSm, - ), - child: Text( - _getShiftType(), - style: UiTypography.footnote2m.copyWith( - color: UiColors.primary, - ), - ), - ), - ], - ], - ), - ), - - Row( - crossAxisAlignment: CrossAxisAlignment.start, + child: Padding( + padding: const EdgeInsets.all(UiConstants.space4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status Badge + if (statusText.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space2), + child: Row( children: [ - // Logo - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - UiColors.primary.withValues(alpha: 0.09), - UiColors.primary.withValues(alpha: 0.03), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), - border: Border.all( - color: UiColors.primary.withValues(alpha: 0.09), - ), - ), - child: widget.shift.logoUrl != null - ? ClipRRect( - borderRadius: BorderRadius.circular(UiConstants.radiusBase), - child: Image.network( - widget.shift.logoUrl!, - fit: BoxFit.contain, - ), - ) - : const Center( - child: Icon( - UiIcons.briefcase, - color: UiColors.primary, - size: UiConstants.iconMd, - ), - ), - ), - const SizedBox(width: UiConstants.space3), - - // Details - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - widget.shift.title, - style: UiTypography.body2m.textPrimary, - overflow: TextOverflow.ellipsis, - ), - Text( - widget.shift.clientName, - style: UiTypography.body3r.textSecondary, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - const SizedBox(width: UiConstants.space2), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - "\$${estimatedTotal.toStringAsFixed(0)}", - style: UiTypography.title1m.textPrimary, - ), - Text( - "\$${widget.shift.hourlyRate.toInt()}/hr · ${duration.toInt()}h", - style: UiTypography.footnote2r.textSecondary, - ), - ], - ), - ], - ), - const SizedBox(height: UiConstants.space2), - - // Date & Time - Multi-Day or Single Day - if (widget.shift.durationDays != null && - widget.shift.durationDays! > 1) ...[ - // Multi-Day Schedule Display - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon( - UiIcons.clock, - size: UiConstants.iconXs, - color: UiColors.primary, - ), - const SizedBox(width: UiConstants.space1), - Text( - t.staff_shifts.details.days( - days: widget.shift.durationDays!, - ), - style: UiTypography.footnote2m.copyWith( - color: UiColors.primary, - ), - ), - ], - ), - const SizedBox(height: UiConstants.space1), - // Mock loop for demo purposes, as we don't have all schedule dates in the model - // In real app, we might need to fetch schedule or iterate if model changes - Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( - '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} – ${_formatTime(widget.shift.endTime)}', - style: UiTypography.footnote2r.copyWith(color: UiColors.primary), - ), - ), - if (widget.shift.durationDays! > 1) - Text( - '... +${widget.shift.durationDays! - 1} more days', - style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)), - ) - ], - ), - ] else ...[ - // Single Day Display - Row( - children: [ - const Icon( - UiIcons.calendar, - size: UiConstants.iconXs, - color: UiColors.iconSecondary, - ), - const SizedBox(width: UiConstants.space1), - Text( - _formatDate(widget.shift.date), - style: UiTypography.footnote1r.textSecondary, - ), - const SizedBox(width: UiConstants.space3), - const Icon( - UiIcons.clock, - size: UiConstants.iconXs, - color: UiColors.iconSecondary, - ), - const SizedBox(width: UiConstants.space1), - Text( - "${_formatTime(widget.shift.startTime)} - ${_formatTime(widget.shift.endTime)}", - style: UiTypography.footnote1r.textSecondary, - ), - ], - ), - ], - const SizedBox(height: UiConstants.space1), - - // Location - Row( - children: [ - const Icon( - UiIcons.mapPin, - size: UiConstants.iconXs, - color: UiColors.iconSecondary, - ), - const SizedBox(width: UiConstants.space1), - Expanded( - child: Text( - widget.shift.locationAddress.isNotEmpty - ? widget.shift.locationAddress - : widget.shift.location, - style: UiTypography.footnote1r.textSecondary, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ], - ), - ), - - // Expanded Content - AnimatedSize( - duration: const Duration(milliseconds: 300), - child: _isExpanded - ? Column( - children: [ - const Divider(height: 1, color: UiColors.border), + if (statusIcon != null) Padding( - padding: const EdgeInsets.all(UiConstants.space4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Stats Row - Row( - children: [ - Expanded( - child: _buildStatCard( - UiIcons.dollar, - "\$${estimatedTotal.toStringAsFixed(0)}", - "Total", - ), - ), - const SizedBox(width: UiConstants.space3), - Expanded( - child: _buildStatCard( - UiIcons.dollar, - "\$${widget.shift.hourlyRate}", - "Hourly Rate", - ), - ), - const SizedBox(width: UiConstants.space3), - Expanded( - child: _buildStatCard( - UiIcons.clock, - "${duration}", - "Hours", - ), - ), - ], - ), - const SizedBox(height: UiConstants.space5), - - // In/Out Time - Row( - children: [ - Expanded( - child: _buildTimeBox( - "CLOCK IN TIME", - widget.shift.startTime, - ), - ), - const SizedBox(width: UiConstants.space3), - Expanded( - child: _buildTimeBox( - "CLOCK OUT TIME", - widget.shift.endTime, - ), - ), - ], - ), - const SizedBox(height: UiConstants.space5), - - // Location - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "LOCATION", - style: UiTypography.footnote2b.copyWith( - color: UiColors.textSecondary, - letterSpacing: 0.5), - ), - const SizedBox(height: UiConstants.space2), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.shift.location.isEmpty - ? "TBD" - : widget.shift.location, - style: UiTypography.title1m.textPrimary, - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: UiConstants.space3), - OutlinedButton.icon( - onPressed: () { - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar( - content: Text( - widget.shift.locationAddress ?? - widget.shift.location, - ), - duration: const Duration( - seconds: 3, - ), - ), - ); - }, - icon: const Icon( - UiIcons.navigation, - size: UiConstants.iconXs, - ), - label: const Text( - "Get direction", - ), - style: OutlinedButton.styleFrom( - foregroundColor: - UiColors.textPrimary, - side: const BorderSide( - color: UiColors.border, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - UiConstants.radiusBase, - ), - ), - padding: const EdgeInsets.symmetric( - horizontal: UiConstants.space3, - vertical: 0, - ), - minimumSize: const Size(0, 32), - ), - ), - ], - ), - const SizedBox(height: UiConstants.space3), - ShiftLocationMap( - shift: widget.shift, - height: 128, - borderRadius: UiConstants.radiusBase, - ), - ], - ), - const SizedBox(height: UiConstants.space5), - - // Additional Info - if (widget.shift.description != null) ...[ - SizedBox( - width: double.infinity, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "ADDITIONAL INFO", - style: UiTypography.footnote2b.copyWith( - color: UiColors.textSecondary, - letterSpacing: 0.5), - ), - const SizedBox(height: UiConstants.space2), - Text( - widget.shift.description!, - style: UiTypography.body2m.textPrimary, - ), - ], - ), - ), - const SizedBox(height: UiConstants.space5), - ], - - // Actions - if (!widget.historyMode) - Padding( - padding: const EdgeInsets.only(top: UiConstants.space2), - child: _buildActions(status), - ), - ], + padding: const EdgeInsets.only(right: UiConstants.space2), + child: Icon( + statusIcon, + size: UiConstants.iconXs, + color: statusColor, + ), + ) + else + Container( + width: 8, + height: 8, + margin: const EdgeInsets.only(right: UiConstants.space2), + decoration: BoxDecoration( + color: statusBg, + shape: BoxShape.circle, + ), + ), + Text( + statusText, + style: UiTypography.footnote2b.copyWith( + color: statusColor, + letterSpacing: 0.5, + ), + ), + // Shift Type Badge + if (status == 'open' || status == 'pending') ...[ + const SizedBox(width: UiConstants.space2), + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: UiColors.primary.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusSm, + ), + child: Text( + _getShiftType(), + style: UiTypography.footnote2m.copyWith( + color: UiColors.primary, + ), ), ), ], - ) - : const SizedBox.shrink(), - ), - ], - ), - ), - ); - } + ], + ), + ), - Widget _buildActions(String? status) { - if (status == 'confirmed') { - return SizedBox( - width: double.infinity, - height: 48, - child: OutlinedButton.icon( - onPressed: widget.onRequestSwap, - icon: const Icon( - UiIcons.swap, - size: UiConstants.iconSm, - ), - label: const Text("Request Swap"), - style: OutlinedButton.styleFrom( - foregroundColor: UiColors.primary, - side: const BorderSide( - color: UiColors.primary, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - UiConstants.radiusBase, - ), - ), - ), - ), - ); - } else if (status == 'swap') { - return Container( - width: double.infinity, - height: 48, - decoration: BoxDecoration( - color: UiColors.tagPending, - border: Border.all( - color: UiColors.textWarning, - ), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - const Icon( - UiIcons.swap, - size: UiConstants.iconSm, - color: UiColors.textWarning, - ), - const SizedBox(width: UiConstants.space2), - Text( - "Swap Pending", - style: UiTypography.body2b.copyWith( - color: UiColors.textWarning, - ), - ), - ], - ), - ); - } else { - // status == 'open' || status == 'pending' or others - return Column( - children: [ - SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: widget.onAccept, - style: ElevatedButton.styleFrom( - backgroundColor: UiColors.primary, - foregroundColor: UiColors.white, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(UiConstants.radiusBase), - ), - ), - child: widget.onAccept == null - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2) - ) // Loading state if callback null? or just Text - : const Text( - "Book Shift", - style: TextStyle( - fontWeight: FontWeight.w600, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Logo + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + UiColors.primary.withValues(alpha: 0.09), + UiColors.primary.withValues(alpha: 0.03), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all( + color: UiColors.primary.withValues(alpha: 0.09), + ), ), - ), - ), - ), - ], - ); - } - } + child: widget.shift.logoUrl != null + ? ClipRRect( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + child: Image.network( + widget.shift.logoUrl!, + fit: BoxFit.contain, + ), + ) + : const Center( + child: Icon( + UiIcons.briefcase, + color: UiColors.primary, + size: UiConstants.iconMd, + ), + ), + ), + const SizedBox(width: UiConstants.space3), - Widget _buildStatCard(IconData icon, String value, String label) { - return Container( - padding: const EdgeInsets.symmetric(vertical: UiConstants.space3), - decoration: BoxDecoration( - color: UiColors.background, - borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), - border: Border.all(color: UiColors.border), - ), - child: Column( - children: [ - Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - color: UiColors.white, - shape: BoxShape.circle, - ), - child: Icon(icon, size: 20, color: UiColors.textSecondary), - ), - const SizedBox(height: UiConstants.space2), - Text( - value, - style: UiTypography.title1m.copyWith(fontWeight: FontWeight.w700).textPrimary, - ), - Text( - label, - style: UiTypography.footnote2r.textSecondary, - ), - ], - ), - ); - } + // Consensed Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + widget.shift.title, + style: UiTypography.body2m.textPrimary, + overflow: TextOverflow.ellipsis, + ), + Text( + widget.shift.clientName, + style: UiTypography.body3r.textSecondary, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: UiConstants.space2), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "\$${estimatedTotal.toStringAsFixed(0)}", + style: UiTypography.title1m.textPrimary, + ), + Text( + "\$${widget.shift.hourlyRate.toInt()}/hr · ${duration.toInt()}h", + style: UiTypography.footnote2r.textSecondary, + ), + ], + ), + ], + ), + const SizedBox(height: UiConstants.space2), - Widget _buildTimeBox(String label, String time) { - return Container( - padding: const EdgeInsets.all(UiConstants.space3), - decoration: BoxDecoration( - color: UiColors.background, - borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), - ), - child: Column( - children: [ - Text( - label, - style: UiTypography.footnote2b.copyWith( - color: UiColors.textSecondary, letterSpacing: 0.5), + // Date & Time + if (widget.shift.durationDays != null && + widget.shift.durationDays! > 1) ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + UiIcons.clock, + size: UiConstants.iconXs, + color: UiColors.primary, + ), + const SizedBox(width: UiConstants.space1), + Text( + t.staff_shifts.details.days( + days: widget.shift.durationDays!, + ), + style: UiTypography.footnote2m.copyWith( + color: UiColors.primary, + ), + ), + ], + ), + const SizedBox(height: UiConstants.space1), + Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} – ${_formatTime(widget.shift.endTime)}', + style: UiTypography.footnote2r.copyWith(color: UiColors.primary), + ), + ), + if (widget.shift.durationDays! > 1) + Text( + '... +${widget.shift.durationDays! - 1} more days', + style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)), + ) + ], + ), + ] else ...[ + Row( + children: [ + const Icon( + UiIcons.calendar, + size: UiConstants.iconXs, + color: UiColors.iconSecondary, + ), + const SizedBox(width: UiConstants.space1), + Text( + _formatDate(widget.shift.date), + style: UiTypography.footnote1r.textSecondary, + ), + const SizedBox(width: UiConstants.space3), + const Icon( + UiIcons.clock, + size: UiConstants.iconXs, + color: UiColors.iconSecondary, + ), + const SizedBox(width: UiConstants.space1), + Text( + "${_formatTime(widget.shift.startTime)} - ${_formatTime(widget.shift.endTime)}", + style: UiTypography.footnote1r.textSecondary, + ), + ], + ), + ], + const SizedBox(height: UiConstants.space1), + + // Location + Row( + children: [ + const Icon( + UiIcons.mapPin, + size: UiConstants.iconXs, + color: UiColors.iconSecondary, + ), + const SizedBox(width: UiConstants.space1), + Expanded( + child: Text( + widget.shift.locationAddress.isNotEmpty + ? widget.shift.locationAddress + : widget.shift.location, + style: UiTypography.footnote1r.textSecondary, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ], ), - const SizedBox(height: UiConstants.space1), - Text( - _formatTime(time), - style: UiTypography.title1m.copyWith(fontWeight: FontWeight.w700).textPrimary, - ), - ], + ), ), ); }