refactor: simplify widget structure and improve date selection logic in clock-in features

This commit is contained in:
Achintha Isuru
2026-03-13 12:14:06 -04:00
parent ec880007d0
commit 2c1c71ad01
4 changed files with 107 additions and 131 deletions

View File

@@ -4,7 +4,6 @@ import 'package:krow_domain/krow_domain.dart';
import '../bloc/clock_in_bloc.dart'; import '../bloc/clock_in_bloc.dart';
import '../bloc/clock_in_event.dart'; import '../bloc/clock_in_event.dart';
import '../bloc/clock_in_state.dart';
import 'clock_in_helpers.dart'; import 'clock_in_helpers.dart';
import 'early_check_in_banner.dart'; import 'early_check_in_banner.dart';
import 'lunch_break_modal.dart'; import 'lunch_break_modal.dart';

View File

@@ -16,7 +16,7 @@ class DateSelector extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final DateTime today = DateTime.now(); final DateTime today = DateTime.now();
final List<DateTime> dates = List.generate(7, (int index) { final List<DateTime> dates = List<DateTime>.generate(7, (int index) {
return today.add(Duration(days: index - 3)); return today.add(Duration(days: index - 3));
}); });
@@ -31,7 +31,7 @@ class DateSelector extends StatelessWidget {
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => onSelect(date), onTap: isToday ? () => onSelect(date) : null,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
@@ -40,58 +40,55 @@ class DateSelector extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? UiColors.primary : UiColors.white, color: isSelected ? UiColors.primary : UiColors.white,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
boxShadow: isSelected
? <BoxShadow>[
BoxShadow(
color: UiColors.primary.withValues(alpha: 0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
]
: <BoxShadow>[],
), ),
child: Column( child: Opacity(
mainAxisAlignment: MainAxisAlignment.center, opacity: isToday ? 1.0 : 0.4,
children: <Widget>[ child: Column(
Text( mainAxisAlignment: MainAxisAlignment.center,
DateFormat('d').format(date), children: <Widget>[
style: UiTypography.title1m.copyWith( Text(
fontWeight: FontWeight.bold, DateFormat('d').format(date),
color: style: UiTypography.title1m.copyWith(
isSelected ? UiColors.white : UiColors.foreground, fontWeight: FontWeight.bold,
), color: isSelected
), ? UiColors.white
const SizedBox(height: 2), : UiColors.foreground,
Text(
DateFormat('E').format(date),
style: UiTypography.footnote2r.copyWith(
color: isSelected
? UiColors.white.withValues(alpha: 0.8)
: UiColors.textInactive,
),
),
const SizedBox(height: UiConstants.space1),
if (hasShift)
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: isSelected ? UiColors.white : UiColors.primary,
shape: BoxShape.circle,
), ),
) ),
else if (isToday && !isSelected) const SizedBox(height: 2),
Container( Text(
width: 6, DateFormat('E').format(date),
height: 6, style: UiTypography.footnote2r.copyWith(
decoration: const BoxDecoration( color: isSelected
color: UiColors.border, ? UiColors.white.withValues(alpha: 0.8)
shape: BoxShape.circle, : UiColors.textInactive,
), ),
) ),
else const SizedBox(height: UiConstants.space1),
const SizedBox(height: 6), if (hasShift)
], Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: isSelected
? UiColors.white
: UiColors.primary,
shape: BoxShape.circle,
),
)
else if (isToday && !isSelected)
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: UiColors.border,
shape: BoxShape.circle,
),
)
else
const SizedBox(height: UiConstants.space3),
],
),
), ),
), ),
), ),
@@ -100,11 +97,13 @@ class DateSelector extends StatelessWidget {
), ),
); );
} }
/// Helper to check if two dates are on the same calendar day (ignoring time).
bool _isSameDay(DateTime a, DateTime b) { bool _isSameDay(DateTime a, DateTime b) {
return a.year == b.year && a.month == b.month && a.day == b.day; return a.year == b.year && a.month == b.month && a.day == b.day;
} }
/// Formats a [DateTime] as an ISO date string (yyyy-MM-dd) for comparison with shift dates.
String _formatDateIso(DateTime date) { String _formatDateIso(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date); return DateFormat('yyyy-MM-dd').format(date);
} }

View File

@@ -155,7 +155,9 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
), ),
), ),
ShiftDetailsHeader(shift: displayShift), ShiftDetailsHeader(shift: displayShift),
const Divider(height: 1, thickness: 0.5), const Divider(height: 1, thickness: 0.5),
ShiftStatsRow( ShiftStatsRow(
estimatedTotal: estimatedTotal, estimatedTotal: estimatedTotal,
hourlyRate: displayShift.hourlyRate, hourlyRate: displayShift.hourlyRate,
@@ -164,7 +166,9 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
hourlyRateLabel: i18n.hourly_rate, hourlyRateLabel: i18n.hourly_rate,
hoursLabel: i18n.hours, hoursLabel: i18n.hours,
), ),
const Divider(height: 1, thickness: 0.5), const Divider(height: 1, thickness: 0.5),
ShiftDateTimeSection( ShiftDateTimeSection(
date: displayShift.date, date: displayShift.date,
endDate: displayShift.endDate, endDate: displayShift.endDate,

View File

@@ -8,93 +8,67 @@ class ShiftDetailsHeader extends StatelessWidget {
final Shift shift; final Shift shift;
/// Creates a [ShiftDetailsHeader]. /// Creates a [ShiftDetailsHeader].
const ShiftDetailsHeader({ const ShiftDetailsHeader({super.key, required this.shift});
super.key,
required this.shift,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(UiConstants.space5), padding: const EdgeInsets.all(UiConstants.space5),
child: IntrinsicHeight( child: Column(
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch, spacing: UiConstants.space4,
spacing: UiConstants.space4, children: [
children: [ // Icon + role name + client name
Container( Row(
width: 114, crossAxisAlignment: CrossAxisAlignment.center,
decoration: BoxDecoration( spacing: UiConstants.space4,
color: UiColors.primary.withAlpha(20), children: [
borderRadius: BorderRadius.circular( Container(
UiConstants.radiusBase, width: 68,
height: 68,
decoration: BoxDecoration(
color: UiColors.primary.withAlpha(20),
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.primary, width: 0.5),
), ),
border: Border.all(color: UiColors.primary), child: const Center(
), child: Icon(
child: const Center( UiIcons.briefcase,
child: Icon( color: UiColors.primary,
UiIcons.briefcase, size: 20,
color: UiColors.primary, ),
size: 24,
), ),
), ),
), Expanded(
Expanded( child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
mainAxisAlignment: MainAxisAlignment.center, Text(shift.title, style: UiTypography.headline1b.textPrimary),
spacing: UiConstants.space3, Text(shift.clientName, style: UiTypography.body1m.textSecondary),
children: [ ],
Text( ),
shift.title,
style: UiTypography.headline1b.textPrimary,
),
Column(
spacing: UiConstants.space1,
children: [
// Client name
Row(
spacing: UiConstants.space1,
children: [
const Icon(
UiIcons.building,
size: 16,
color: UiColors.textSecondary,
),
Expanded(
child: Text(
shift.clientName,
style: UiTypography.body1m.textSecondary,
),
),
],
),
// Location address (if available)
Row(
spacing: UiConstants.space1,
children: [
const Icon(
UiIcons.mapPin,
size: 16,
color: UiColors.textSecondary,
),
Expanded(
child: Text(
shift.locationAddress,
style: UiTypography.body2r.textSecondary,
),
),
],
),
],
),
],
), ),
), ],
], ),
),
// Location address
Row(
spacing: UiConstants.space1,
children: [
const Icon(
UiIcons.mapPin,
size: 16,
color: UiColors.textSecondary,
),
Expanded(
child: Text(
shift.locationAddress,
style: UiTypography.body2r.textSecondary,
),
),
],
),
],
), ),
); );
} }