refactor: simplify widget structure and improve date selection logic in clock-in features
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user