refactor: extract invoice approval and dispute logic into a dedicated ShiftCompletionReviewBloc.

This commit is contained in:
Achintha Isuru
2026-02-28 16:02:10 -05:00
parent 5a79a4e517
commit 76424b1b1f
14 changed files with 273 additions and 117 deletions

View File

@@ -102,7 +102,7 @@ extension ClientNavigator on IModularNavigator {
/// Navigates to the full list of invoices awaiting approval. /// Navigates to the full list of invoices awaiting approval.
void toAwaitingApproval({Object? arguments}) { void toAwaitingApproval({Object? arguments}) {
pushNamed(ClientPaths.awaitingApproval, arguments: arguments); navigate(ClientPaths.awaitingApproval, arguments: arguments);
} }
/// Navigates to the Invoice Ready page. /// Navigates to the Invoice Ready page.

View File

@@ -3,7 +3,6 @@ import '../ui_constants.dart';
/// A custom button widget with different variants and icon support. /// A custom button widget with different variants and icon support.
class UiButton extends StatelessWidget { class UiButton extends StatelessWidget {
/// Creates a [UiButton] with a custom button builder. /// Creates a [UiButton] with a custom button builder.
const UiButton({ const UiButton({
super.key, super.key,
@@ -17,6 +16,7 @@ class UiButton extends StatelessWidget {
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.large, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
this.isLoading = false,
}) : assert( }) : assert(
text != null || child != null, text != null || child != null,
'Either text or child must be provided', 'Either text or child must be provided',
@@ -34,6 +34,7 @@ class UiButton extends StatelessWidget {
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.large, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
this.isLoading = false,
}) : buttonBuilder = _elevatedButtonBuilder, }) : buttonBuilder = _elevatedButtonBuilder,
assert( assert(
text != null || child != null, text != null || child != null,
@@ -50,8 +51,9 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.large, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
this.isLoading = false,
}) : buttonBuilder = _outlinedButtonBuilder, }) : buttonBuilder = _outlinedButtonBuilder,
assert( assert(
text != null || child != null, text != null || child != null,
@@ -70,6 +72,7 @@ class UiButton extends StatelessWidget {
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.large, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
this.isLoading = false,
}) : buttonBuilder = _textButtonBuilder, }) : buttonBuilder = _textButtonBuilder,
assert( assert(
text != null || child != null, text != null || child != null,
@@ -88,11 +91,13 @@ class UiButton extends StatelessWidget {
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.large, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
this.isLoading = false,
}) : buttonBuilder = _textButtonBuilder, }) : buttonBuilder = _textButtonBuilder,
assert( assert(
text != null || child != null, text != null || child != null,
'Either text or child must be provided', 'Either text or child must be provided',
); );
/// The text to display on the button. /// The text to display on the button.
final String? text; final String? text;
@@ -129,18 +134,21 @@ class UiButton extends StatelessWidget {
) )
buttonBuilder; buttonBuilder;
/// Whether to show a loading indicator.
final bool isLoading;
@override @override
/// Builds the button UI. /// Builds the button UI.
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ButtonStyle mergedStyle = style != null final ButtonStyle mergedStyle = style != null
? _getSizeStyle().merge(style) ? _getSizeStyle().merge(style)
: _getSizeStyle(); : _getSizeStyle();
final Widget button = buttonBuilder( final Widget button = buttonBuilder(
context, context,
onPressed, isLoading ? null : onPressed,
mergedStyle, mergedStyle,
_buildButtonContent(), isLoading ? _buildLoadingContent() : _buildButtonContent(),
); );
if (fullWidth) { if (fullWidth) {
@@ -150,6 +158,15 @@ class UiButton extends StatelessWidget {
return button; return button;
} }
/// Builds the loading indicator.
Widget _buildLoadingContent() {
return const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
);
}
/// Gets the style based on the button size. /// Gets the style based on the button size.
ButtonStyle _getSizeStyle() { ButtonStyle _getSizeStyle() {
switch (size) { switch (size) {

View File

@@ -12,6 +12,7 @@ import 'domain/usecases/get_spending_breakdown.dart';
import 'domain/usecases/approve_invoice.dart'; import 'domain/usecases/approve_invoice.dart';
import 'domain/usecases/dispute_invoice.dart'; import 'domain/usecases/dispute_invoice.dart';
import 'presentation/blocs/billing_bloc.dart'; import 'presentation/blocs/billing_bloc.dart';
import 'presentation/blocs/shift_completion_review/shift_completion_review_bloc.dart';
import 'presentation/models/billing_invoice_model.dart'; import 'presentation/models/billing_invoice_model.dart';
import 'presentation/pages/billing_page.dart'; import 'presentation/pages/billing_page.dart';
import 'presentation/pages/completion_review_page.dart'; import 'presentation/pages/completion_review_page.dart';
@@ -44,6 +45,10 @@ class BillingModule extends Module {
getPendingInvoices: i.get<GetPendingInvoicesUseCase>(), getPendingInvoices: i.get<GetPendingInvoicesUseCase>(),
getInvoiceHistory: i.get<GetInvoiceHistoryUseCase>(), getInvoiceHistory: i.get<GetInvoiceHistoryUseCase>(),
getSpendingBreakdown: i.get<GetSpendingBreakdownUseCase>(), getSpendingBreakdown: i.get<GetSpendingBreakdownUseCase>(),
),
);
i.add<ShiftCompletionReviewBloc>(
() => ShiftCompletionReviewBloc(
approveInvoice: i.get<ApproveInvoiceUseCase>(), approveInvoice: i.get<ApproveInvoiceUseCase>(),
disputeInvoice: i.get<DisputeInvoiceUseCase>(), disputeInvoice: i.get<DisputeInvoiceUseCase>(),
), ),

View File

@@ -8,8 +8,6 @@ import '../../domain/usecases/get_invoice_history.dart';
import '../../domain/usecases/get_pending_invoices.dart'; import '../../domain/usecases/get_pending_invoices.dart';
import '../../domain/usecases/get_savings_amount.dart'; import '../../domain/usecases/get_savings_amount.dart';
import '../../domain/usecases/get_spending_breakdown.dart'; import '../../domain/usecases/get_spending_breakdown.dart';
import '../../domain/usecases/approve_invoice.dart';
import '../../domain/usecases/dispute_invoice.dart';
import '../models/billing_invoice_model.dart'; import '../models/billing_invoice_model.dart';
import '../models/spending_breakdown_model.dart'; import '../models/spending_breakdown_model.dart';
import 'billing_event.dart'; import 'billing_event.dart';
@@ -26,21 +24,15 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
required GetPendingInvoicesUseCase getPendingInvoices, required GetPendingInvoicesUseCase getPendingInvoices,
required GetInvoiceHistoryUseCase getInvoiceHistory, required GetInvoiceHistoryUseCase getInvoiceHistory,
required GetSpendingBreakdownUseCase getSpendingBreakdown, required GetSpendingBreakdownUseCase getSpendingBreakdown,
required ApproveInvoiceUseCase approveInvoice,
required DisputeInvoiceUseCase disputeInvoice,
}) : _getBankAccounts = getBankAccounts, }) : _getBankAccounts = getBankAccounts,
_getCurrentBillAmount = getCurrentBillAmount, _getCurrentBillAmount = getCurrentBillAmount,
_getSavingsAmount = getSavingsAmount, _getSavingsAmount = getSavingsAmount,
_getPendingInvoices = getPendingInvoices, _getPendingInvoices = getPendingInvoices,
_getInvoiceHistory = getInvoiceHistory, _getInvoiceHistory = getInvoiceHistory,
_getSpendingBreakdown = getSpendingBreakdown, _getSpendingBreakdown = getSpendingBreakdown,
_approveInvoice = approveInvoice,
_disputeInvoice = disputeInvoice,
super(const BillingState()) { super(const BillingState()) {
on<BillingLoadStarted>(_onLoadStarted); on<BillingLoadStarted>(_onLoadStarted);
on<BillingPeriodChanged>(_onPeriodChanged); on<BillingPeriodChanged>(_onPeriodChanged);
on<BillingInvoiceApproved>(_onInvoiceApproved);
on<BillingInvoiceDisputed>(_onInvoiceDisputed);
} }
final GetBankAccountsUseCase _getBankAccounts; final GetBankAccountsUseCase _getBankAccounts;
@@ -49,8 +41,6 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
final GetPendingInvoicesUseCase _getPendingInvoices; final GetPendingInvoicesUseCase _getPendingInvoices;
final GetInvoiceHistoryUseCase _getInvoiceHistory; final GetInvoiceHistoryUseCase _getInvoiceHistory;
final GetSpendingBreakdownUseCase _getSpendingBreakdown; final GetSpendingBreakdownUseCase _getSpendingBreakdown;
final ApproveInvoiceUseCase _approveInvoice;
final DisputeInvoiceUseCase _disputeInvoice;
Future<void> _onLoadStarted( Future<void> _onLoadStarted(
BillingLoadStarted event, BillingLoadStarted event,
@@ -136,38 +126,6 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
); );
} }
Future<void> _onInvoiceApproved(
BillingInvoiceApproved event,
Emitter<BillingState> emit,
) async {
await handleError(
emit: emit.call,
action: () async {
await _approveInvoice.call(event.invoiceId);
add(const BillingLoadStarted());
},
onError: (String errorKey) =>
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
);
}
Future<void> _onInvoiceDisputed(
BillingInvoiceDisputed event,
Emitter<BillingState> emit,
) async {
await handleError(
emit: emit.call,
action: () async {
await _disputeInvoice.call(
DisputeInvoiceParams(id: event.invoiceId, reason: event.reason),
);
add(const BillingLoadStarted());
},
onError: (String errorKey) =>
state.copyWith(status: BillingStatus.failure, errorMessage: errorKey),
);
}
BillingInvoice _mapInvoiceToUiModel(Invoice invoice) { BillingInvoice _mapInvoiceToUiModel(Invoice invoice) {
final DateFormat formatter = DateFormat('EEEE, MMMM d'); final DateFormat formatter = DateFormat('EEEE, MMMM d');
final String dateLabel = invoice.issueDate == null final String dateLabel = invoice.issueDate == null

View File

@@ -24,20 +24,3 @@ class BillingPeriodChanged extends BillingEvent {
@override @override
List<Object?> get props => <Object?>[period]; List<Object?> get props => <Object?>[period];
} }
class BillingInvoiceApproved extends BillingEvent {
const BillingInvoiceApproved(this.invoiceId);
final String invoiceId;
@override
List<Object?> get props => <Object?>[invoiceId];
}
class BillingInvoiceDisputed extends BillingEvent {
const BillingInvoiceDisputed(this.invoiceId, this.reason);
final String invoiceId;
final String reason;
@override
List<Object?> get props => <Object?>[invoiceId, reason];
}

View File

@@ -0,0 +1,71 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import '../../../domain/usecases/approve_invoice.dart';
import '../../../domain/usecases/dispute_invoice.dart';
import 'shift_completion_review_event.dart';
import 'shift_completion_review_state.dart';
class ShiftCompletionReviewBloc
extends Bloc<ShiftCompletionReviewEvent, ShiftCompletionReviewState>
with BlocErrorHandler<ShiftCompletionReviewState> {
ShiftCompletionReviewBloc({
required ApproveInvoiceUseCase approveInvoice,
required DisputeInvoiceUseCase disputeInvoice,
}) : _approveInvoice = approveInvoice,
_disputeInvoice = disputeInvoice,
super(const ShiftCompletionReviewState()) {
on<ShiftCompletionReviewApproved>(_onApproved);
on<ShiftCompletionReviewDisputed>(_onDisputed);
}
final ApproveInvoiceUseCase _approveInvoice;
final DisputeInvoiceUseCase _disputeInvoice;
Future<void> _onApproved(
ShiftCompletionReviewApproved event,
Emitter<ShiftCompletionReviewState> emit,
) async {
emit(state.copyWith(status: ShiftCompletionReviewStatus.loading));
await handleError(
emit: emit.call,
action: () async {
await _approveInvoice.call(event.invoiceId);
emit(
state.copyWith(
status: ShiftCompletionReviewStatus.success,
message: 'approved',
),
);
},
onError: (String errorKey) => state.copyWith(
status: ShiftCompletionReviewStatus.failure,
errorMessage: errorKey,
),
);
}
Future<void> _onDisputed(
ShiftCompletionReviewDisputed event,
Emitter<ShiftCompletionReviewState> emit,
) async {
emit(state.copyWith(status: ShiftCompletionReviewStatus.loading));
await handleError(
emit: emit.call,
action: () async {
await _disputeInvoice.call(
DisputeInvoiceParams(id: event.invoiceId, reason: event.reason),
);
emit(
state.copyWith(
status: ShiftCompletionReviewStatus.success,
message: 'disputed',
),
);
},
onError: (String errorKey) => state.copyWith(
status: ShiftCompletionReviewStatus.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
/// Base class for all shift completion review events.
abstract class ShiftCompletionReviewEvent extends Equatable {
/// Creates a [ShiftCompletionReviewEvent].
const ShiftCompletionReviewEvent();
@override
List<Object?> get props => <Object?>[];
}
/// Event triggered when an invoice is approved.
class ShiftCompletionReviewApproved extends ShiftCompletionReviewEvent {
/// Creates a [ShiftCompletionReviewApproved] event.
const ShiftCompletionReviewApproved(this.invoiceId);
/// The ID of the invoice to approve.
final String invoiceId;
@override
List<Object?> get props => <Object?>[invoiceId];
}
/// Event triggered when an invoice is disputed.
class ShiftCompletionReviewDisputed extends ShiftCompletionReviewEvent {
/// Creates a [ShiftCompletionReviewDisputed] event.
const ShiftCompletionReviewDisputed(this.invoiceId, this.reason);
/// The ID of the invoice to dispute.
final String invoiceId;
/// The reason for the dispute.
final String reason;
@override
List<Object?> get props => <Object?>[invoiceId, reason];
}

View File

@@ -0,0 +1,51 @@
import 'package:equatable/equatable.dart';
/// Status of the shift completion review process.
enum ShiftCompletionReviewStatus {
/// Initial state.
initial,
/// Loading state.
loading,
/// Success state.
success,
/// Failure state.
failure,
}
/// State for the [ShiftCompletionReviewBloc].
class ShiftCompletionReviewState extends Equatable {
/// Creates a [ShiftCompletionReviewState].
const ShiftCompletionReviewState({
this.status = ShiftCompletionReviewStatus.initial,
this.message,
this.errorMessage,
});
/// Current status of the process.
final ShiftCompletionReviewStatus status;
/// Success message (e.g., 'approved' or 'disputed').
final String? message;
/// Error message to display if [status] is [ShiftCompletionReviewStatus.failure].
final String? errorMessage;
/// Creates a copy of this state with the given fields replaced.
ShiftCompletionReviewState copyWith({
ShiftCompletionReviewStatus? status,
String? message,
String? errorMessage,
}) {
return ShiftCompletionReviewState(
status: status ?? this.status,
message: message ?? this.message,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => <Object?>[status, message, errorMessage];
}

View File

@@ -11,7 +11,7 @@ class BillingInvoice extends Equatable {
required this.workersCount, required this.workersCount,
required this.totalHours, required this.totalHours,
required this.status, required this.status,
this.workers = const [], this.workers = const <BillingWorkerRecord>[],
this.startTime, this.startTime,
this.endTime, this.endTime,
}); });
@@ -70,7 +70,7 @@ class BillingWorkerRecord extends Equatable {
final String? workerAvatarUrl; final String? workerAvatarUrl;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
workerName, workerName,
roleName, roleName,
totalAmount, totalAmount,

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import '../models/billing_invoice_model.dart'; import '../models/billing_invoice_model.dart';
@@ -48,6 +49,7 @@ class _ShiftCompletionReviewPageState extends State<ShiftCompletionReviewPage> {
title: invoice.title, title: invoice.title,
subtitle: invoice.clientName, subtitle: invoice.clientName,
showBackButton: true, showBackButton: true,
onLeadingPressed: () => Modular.to.toAwaitingApproval(),
), ),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(

View File

@@ -37,7 +37,7 @@ class InvoiceReadyView extends StatelessWidget {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: <Widget>[
const Icon( const Icon(
UiIcons.file, UiIcons.file,
size: 64, size: 64,
@@ -81,7 +81,7 @@ class _InvoiceSummaryCard extends StatelessWidget {
color: UiColors.white, color: UiColors.white,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withValues(alpha: 0.04), color: UiColors.black.withValues(alpha: 0.04),
blurRadius: 10, blurRadius: 10,
@@ -91,10 +91,10 @@ class _InvoiceSummaryCard extends StatelessWidget {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
@@ -124,10 +124,10 @@ class _InvoiceSummaryCard extends StatelessWidget {
const Divider(height: 32), const Divider(height: 32),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
'TOTAL AMOUNT', 'TOTAL AMOUNT',
style: UiTypography.titleUppercase4m.textSecondary, style: UiTypography.titleUppercase4m.textSecondary,

View File

@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import '../blocs/billing_bloc.dart'; import '../blocs/billing_bloc.dart';
import '../blocs/billing_state.dart'; import '../blocs/billing_state.dart';
@@ -20,6 +21,7 @@ class PendingInvoicesPage extends StatelessWidget {
appBar: UiAppBar( appBar: UiAppBar(
title: t.client_billing.awaiting_approval, title: t.client_billing.awaiting_approval,
showBackButton: true, showBackButton: true,
onLeadingPressed: () => Modular.to.toClientBilling(),
), ),
body: _buildBody(context, state), body: _buildBody(context, state),
); );

View File

@@ -1,11 +1,13 @@
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import '../../blocs/billing_bloc.dart'; import '../../blocs/shift_completion_review/shift_completion_review_bloc.dart';
import '../../blocs/billing_event.dart'; import '../../blocs/shift_completion_review/shift_completion_review_event.dart';
import '../../blocs/shift_completion_review/shift_completion_review_state.dart';
class CompletionReviewActions extends StatelessWidget { class CompletionReviewActions extends StatelessWidget {
const CompletionReviewActions({required this.invoiceId, super.key}); const CompletionReviewActions({required this.invoiceId, super.key});
@@ -14,47 +16,80 @@ class CompletionReviewActions extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return BlocProvider<ShiftCompletionReviewBloc>.value(
spacing: UiConstants.space2, value: Modular.get<ShiftCompletionReviewBloc>(),
children: <Widget>[ child:
Expanded( BlocConsumer<ShiftCompletionReviewBloc, ShiftCompletionReviewState>(
child: UiButton.secondary( listener: (BuildContext context, ShiftCompletionReviewState state) {
text: t.client_billing.actions.flag_review, if (state.status == ShiftCompletionReviewStatus.success) {
leadingIcon: UiIcons.warning, final String message = state.message == 'approved'
onPressed: () => _showFlagDialog(context), ? t.client_billing.approved_success
size: UiButtonSize.large, : t.client_billing.flagged_success;
style: OutlinedButton.styleFrom( final UiSnackbarType type = state.message == 'approved'
foregroundColor: UiColors.destructive, ? UiSnackbarType.success
side: BorderSide.none, : UiSnackbarType.warning;
),
), UiSnackbar.show(context, message: message, type: type);
), Modular.to.toAwaitingApproval();
Expanded( } else if (state.status == ShiftCompletionReviewStatus.failure) {
child: UiButton.primary( UiSnackbar.show(
text: t.client_billing.actions.approve_pay, context,
leadingIcon: UiIcons.checkCircle, message: state.errorMessage ?? t.errors.generic.unknown,
onPressed: () { type: UiSnackbarType.error,
Modular.get<BillingBloc>().add(BillingInvoiceApproved(invoiceId)); );
Modular.to.pop(); }
UiSnackbar.show( },
context, builder: (BuildContext context, ShiftCompletionReviewState state) {
message: t.client_billing.approved_success, final bool isLoading =
type: UiSnackbarType.success, state.status == ShiftCompletionReviewStatus.loading;
return Row(
spacing: UiConstants.space2,
children: <Widget>[
Expanded(
child: UiButton.secondary(
text: t.client_billing.actions.flag_review,
leadingIcon: UiIcons.warning,
onPressed: isLoading
? null
: () => _showFlagDialog(context, state),
size: UiButtonSize.large,
style: OutlinedButton.styleFrom(
foregroundColor: UiColors.destructive,
side: BorderSide.none,
),
),
),
Expanded(
child: UiButton.primary(
text: t.client_billing.actions.approve_pay,
leadingIcon: isLoading ? null : UiIcons.checkCircle,
isLoading: isLoading,
onPressed: isLoading
? null
: () {
BlocProvider.of<ShiftCompletionReviewBloc>(
context,
).add(ShiftCompletionReviewApproved(invoiceId));
},
size: UiButtonSize.large,
),
),
],
); );
}, },
size: UiButtonSize.large,
), ),
),
],
); );
} }
void _showFlagDialog(BuildContext context) { void _showFlagDialog(BuildContext context, ShiftCompletionReviewState state) {
final TextEditingController controller = TextEditingController(); final TextEditingController controller = TextEditingController();
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext dialogContext) => AlertDialog( builder: (BuildContext dialogContext) => AlertDialog(
title: Text(t.client_billing.flag_dialog.title), title: Text(t.client_billing.flag_dialog.title),
surfaceTintColor: Colors.white,
backgroundColor: Colors.white,
content: TextField( content: TextField(
controller: controller, controller: controller,
decoration: InputDecoration( decoration: InputDecoration(
@@ -70,15 +105,10 @@ class CompletionReviewActions extends StatelessWidget {
TextButton( TextButton(
onPressed: () { onPressed: () {
if (controller.text.isNotEmpty) { if (controller.text.isNotEmpty) {
Modular.get<BillingBloc>().add( BlocProvider.of<ShiftCompletionReviewBloc>(context).add(
BillingInvoiceDisputed(invoiceId, controller.text), ShiftCompletionReviewDisputed(invoiceId, controller.text),
);
Modular.to.toClientBilling();
UiSnackbar.show(
context,
message: t.client_billing.flagged_success,
type: UiSnackbarType.warning,
); );
Navigator.pop(dialogContext);
} }
}, },
child: Text(t.client_billing.flag_dialog.button), child: Text(t.client_billing.flag_dialog.button),

View File

@@ -15,9 +15,9 @@ class CompletionReviewAmount extends StatelessWidget {
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(UiConstants.space6), padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEFF6FF), color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
border: Border.all(color: const Color(0xFFDBEAFE)), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[