feat(shifts): implement submit for approval functionality

- Added `submitForApproval` method to `ShiftsRepositoryInterface` and its implementation in `ShiftsRepositoryImpl`.
- Created `SubmitForApprovalUseCase` to handle the submission logic.
- Updated `ShiftsBloc` to handle `SubmitForApprovalEvent` and manage submission state.
- Enhanced `HistoryShiftsTab` and `MyShiftsTab` to support submission actions and display appropriate UI feedback.
- Refactored date utilities for better calendar management and filtering of past shifts.
- Improved UI components for better spacing and alignment.
- Localized success messages for shift submission actions.
This commit is contained in:
Achintha Isuru
2026-03-18 14:37:55 -04:00
parent 3e5b6af8dc
commit 3a5f2cc9c6
50 changed files with 1269 additions and 408 deletions

View File

@@ -32,14 +32,11 @@ class _TimeCardPageState extends State<TimeCardPage> {
@override
Widget build(BuildContext context) {
final Translations t = Translations.of(context);
return BlocProvider.value(
value: _bloc,
child: Scaffold(
appBar: UiAppBar(
title: t.staff_time_card.title,
showBackButton: true,
),
body: BlocConsumer<TimeCardBloc, TimeCardState>(
return Scaffold(
appBar: UiAppBar(title: t.staff_time_card.title, showBackButton: true),
body: BlocProvider.value(
value: _bloc,
child: BlocConsumer<TimeCardBloc, TimeCardState>(
listener: (BuildContext context, TimeCardState state) {
if (state is TimeCardError) {
UiSnackbar.show(

View File

@@ -17,9 +17,7 @@ class ShiftHistoryList extends StatelessWidget {
children: <Widget>[
Text(
t.staff_time_card.shift_history,
style: UiTypography.title2b.copyWith(
color: UiColors.textPrimary,
),
style: UiTypography.title2b,
),
const SizedBox(height: UiConstants.space3),
if (timesheets.isEmpty)

View File

@@ -6,7 +6,6 @@ import 'package:krow_domain/krow_domain.dart';
/// A card widget displaying details of a single shift/timecard.
class TimesheetCard extends StatelessWidget {
const TimesheetCard({super.key, required this.timesheet});
final TimeCardEntry timesheet;
@@ -25,9 +24,10 @@ class TimesheetCard extends StatelessWidget {
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
border: Border.all(color: UiColors.border, width: 0.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -60,20 +60,22 @@ class TimesheetCard extends StatelessWidget {
if (timesheet.clockInAt != null && timesheet.clockOutAt != null)
_IconText(
icon: UiIcons.clock,
text: '${DateFormat('h:mm a').format(timesheet.clockInAt!)} - ${DateFormat('h:mm a').format(timesheet.clockOutAt!)}',
text:
'${DateFormat('h:mm a').format(timesheet.clockInAt!)} - ${DateFormat('h:mm a').format(timesheet.clockOutAt!)}',
),
if (timesheet.location != null)
_IconText(icon: UiIcons.mapPin, text: timesheet.location!),
],
),
const SizedBox(height: UiConstants.space3),
const SizedBox(height: UiConstants.space5),
Container(
padding: const EdgeInsets.only(top: UiConstants.space3),
padding: const EdgeInsets.only(top: UiConstants.space4),
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: UiColors.border)),
border: Border(top: BorderSide(color: UiColors.border, width: 0.5)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'${totalHours.toStringAsFixed(1)} ${t.staff_time_card.hours} @ \$${hourlyRate.toStringAsFixed(2)}${t.staff_time_card.per_hr}',
@@ -81,7 +83,7 @@ class TimesheetCard extends StatelessWidget {
),
Text(
'\$${totalPay.toStringAsFixed(2)}',
style: UiTypography.title2b.primary,
style: UiTypography.title1b,
),
],
),
@@ -93,7 +95,6 @@ class TimesheetCard extends StatelessWidget {
}
class _IconText extends StatelessWidget {
const _IconText({required this.icon, required this.text});
final IconData icon;
final String text;
@@ -105,10 +106,7 @@ class _IconText extends StatelessWidget {
children: <Widget>[
Icon(icon, size: 14, color: UiColors.iconSecondary),
const SizedBox(width: UiConstants.space1),
Text(
text,
style: UiTypography.body2r.textSecondary,
),
Text(text, style: UiTypography.body2r.textSecondary),
],
);
}