refactor: extract invoice approval and dispute logic into a dedicated ShiftCompletionReviewBloc.
This commit is contained in:
@@ -102,7 +102,7 @@ extension ClientNavigator on IModularNavigator {
|
||||
|
||||
/// Navigates to the full list of invoices awaiting approval.
|
||||
void toAwaitingApproval({Object? arguments}) {
|
||||
pushNamed(ClientPaths.awaitingApproval, arguments: arguments);
|
||||
navigate(ClientPaths.awaitingApproval, arguments: arguments);
|
||||
}
|
||||
|
||||
/// Navigates to the Invoice Ready page.
|
||||
|
||||
@@ -3,7 +3,6 @@ import '../ui_constants.dart';
|
||||
|
||||
/// A custom button widget with different variants and icon support.
|
||||
class UiButton extends StatelessWidget {
|
||||
|
||||
/// Creates a [UiButton] with a custom button builder.
|
||||
const UiButton({
|
||||
super.key,
|
||||
@@ -17,6 +16,7 @@ class UiButton extends StatelessWidget {
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
this.isLoading = false,
|
||||
}) : assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
@@ -34,6 +34,7 @@ class UiButton extends StatelessWidget {
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
this.isLoading = false,
|
||||
}) : buttonBuilder = _elevatedButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
@@ -52,6 +53,7 @@ class UiButton extends StatelessWidget {
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
this.isLoading = false,
|
||||
}) : buttonBuilder = _outlinedButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
@@ -70,6 +72,7 @@ class UiButton extends StatelessWidget {
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
this.isLoading = false,
|
||||
}) : buttonBuilder = _textButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
@@ -88,11 +91,13 @@ class UiButton extends StatelessWidget {
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
this.isLoading = false,
|
||||
}) : buttonBuilder = _textButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
);
|
||||
|
||||
/// The text to display on the button.
|
||||
final String? text;
|
||||
|
||||
@@ -129,6 +134,9 @@ class UiButton extends StatelessWidget {
|
||||
)
|
||||
buttonBuilder;
|
||||
|
||||
/// Whether to show a loading indicator.
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
/// Builds the button UI.
|
||||
Widget build(BuildContext context) {
|
||||
@@ -138,9 +146,9 @@ class UiButton extends StatelessWidget {
|
||||
|
||||
final Widget button = buttonBuilder(
|
||||
context,
|
||||
onPressed,
|
||||
isLoading ? null : onPressed,
|
||||
mergedStyle,
|
||||
_buildButtonContent(),
|
||||
isLoading ? _buildLoadingContent() : _buildButtonContent(),
|
||||
);
|
||||
|
||||
if (fullWidth) {
|
||||
@@ -150,6 +158,15 @@ class UiButton extends StatelessWidget {
|
||||
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.
|
||||
ButtonStyle _getSizeStyle() {
|
||||
switch (size) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'domain/usecases/get_spending_breakdown.dart';
|
||||
import 'domain/usecases/approve_invoice.dart';
|
||||
import 'domain/usecases/dispute_invoice.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/pages/billing_page.dart';
|
||||
import 'presentation/pages/completion_review_page.dart';
|
||||
@@ -44,6 +45,10 @@ class BillingModule extends Module {
|
||||
getPendingInvoices: i.get<GetPendingInvoicesUseCase>(),
|
||||
getInvoiceHistory: i.get<GetInvoiceHistoryUseCase>(),
|
||||
getSpendingBreakdown: i.get<GetSpendingBreakdownUseCase>(),
|
||||
),
|
||||
);
|
||||
i.add<ShiftCompletionReviewBloc>(
|
||||
() => ShiftCompletionReviewBloc(
|
||||
approveInvoice: i.get<ApproveInvoiceUseCase>(),
|
||||
disputeInvoice: i.get<DisputeInvoiceUseCase>(),
|
||||
),
|
||||
|
||||
@@ -8,8 +8,6 @@ import '../../domain/usecases/get_invoice_history.dart';
|
||||
import '../../domain/usecases/get_pending_invoices.dart';
|
||||
import '../../domain/usecases/get_savings_amount.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/spending_breakdown_model.dart';
|
||||
import 'billing_event.dart';
|
||||
@@ -26,21 +24,15 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
||||
required GetPendingInvoicesUseCase getPendingInvoices,
|
||||
required GetInvoiceHistoryUseCase getInvoiceHistory,
|
||||
required GetSpendingBreakdownUseCase getSpendingBreakdown,
|
||||
required ApproveInvoiceUseCase approveInvoice,
|
||||
required DisputeInvoiceUseCase disputeInvoice,
|
||||
}) : _getBankAccounts = getBankAccounts,
|
||||
_getCurrentBillAmount = getCurrentBillAmount,
|
||||
_getSavingsAmount = getSavingsAmount,
|
||||
_getPendingInvoices = getPendingInvoices,
|
||||
_getInvoiceHistory = getInvoiceHistory,
|
||||
_getSpendingBreakdown = getSpendingBreakdown,
|
||||
_approveInvoice = approveInvoice,
|
||||
_disputeInvoice = disputeInvoice,
|
||||
super(const BillingState()) {
|
||||
on<BillingLoadStarted>(_onLoadStarted);
|
||||
on<BillingPeriodChanged>(_onPeriodChanged);
|
||||
on<BillingInvoiceApproved>(_onInvoiceApproved);
|
||||
on<BillingInvoiceDisputed>(_onInvoiceDisputed);
|
||||
}
|
||||
|
||||
final GetBankAccountsUseCase _getBankAccounts;
|
||||
@@ -49,8 +41,6 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
||||
final GetPendingInvoicesUseCase _getPendingInvoices;
|
||||
final GetInvoiceHistoryUseCase _getInvoiceHistory;
|
||||
final GetSpendingBreakdownUseCase _getSpendingBreakdown;
|
||||
final ApproveInvoiceUseCase _approveInvoice;
|
||||
final DisputeInvoiceUseCase _disputeInvoice;
|
||||
|
||||
Future<void> _onLoadStarted(
|
||||
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) {
|
||||
final DateFormat formatter = DateFormat('EEEE, MMMM d');
|
||||
final String dateLabel = invoice.issueDate == null
|
||||
|
||||
@@ -24,20 +24,3 @@ class BillingPeriodChanged extends BillingEvent {
|
||||
@override
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class BillingInvoice extends Equatable {
|
||||
required this.workersCount,
|
||||
required this.totalHours,
|
||||
required this.status,
|
||||
this.workers = const [],
|
||||
this.workers = const <BillingWorkerRecord>[],
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
});
|
||||
@@ -70,7 +70,7 @@ class BillingWorkerRecord extends Equatable {
|
||||
final String? workerAvatarUrl;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
List<Object?> get props => <Object?>[
|
||||
workerName,
|
||||
roleName,
|
||||
totalAmount,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../models/billing_invoice_model.dart';
|
||||
|
||||
@@ -48,6 +49,7 @@ class _ShiftCompletionReviewPageState extends State<ShiftCompletionReviewPage> {
|
||||
title: invoice.title,
|
||||
subtitle: invoice.clientName,
|
||||
showBackButton: true,
|
||||
onLeadingPressed: () => Modular.to.toAwaitingApproval(),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
|
||||
@@ -37,7 +37,7 @@ class InvoiceReadyView extends StatelessWidget {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.file,
|
||||
size: 64,
|
||||
@@ -81,7 +81,7 @@ class _InvoiceSummaryCard extends StatelessWidget {
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
@@ -91,10 +91,10 @@ class _InvoiceSummaryCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
@@ -124,10 +124,10 @@ class _InvoiceSummaryCard extends StatelessWidget {
|
||||
const Divider(height: 32),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'TOTAL AMOUNT',
|
||||
style: UiTypography.titleUppercase4m.textSecondary,
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../blocs/billing_bloc.dart';
|
||||
import '../blocs/billing_state.dart';
|
||||
@@ -20,6 +21,7 @@ class PendingInvoicesPage extends StatelessWidget {
|
||||
appBar: UiAppBar(
|
||||
title: t.client_billing.awaiting_approval,
|
||||
showBackButton: true,
|
||||
onLeadingPressed: () => Modular.to.toClientBilling(),
|
||||
),
|
||||
body: _buildBody(context, state),
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
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';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../../blocs/billing_bloc.dart';
|
||||
import '../../blocs/billing_event.dart';
|
||||
import '../../blocs/shift_completion_review/shift_completion_review_bloc.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 {
|
||||
const CompletionReviewActions({required this.invoiceId, super.key});
|
||||
@@ -14,6 +16,33 @@ class CompletionReviewActions extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ShiftCompletionReviewBloc>.value(
|
||||
value: Modular.get<ShiftCompletionReviewBloc>(),
|
||||
child:
|
||||
BlocConsumer<ShiftCompletionReviewBloc, ShiftCompletionReviewState>(
|
||||
listener: (BuildContext context, ShiftCompletionReviewState state) {
|
||||
if (state.status == ShiftCompletionReviewStatus.success) {
|
||||
final String message = state.message == 'approved'
|
||||
? t.client_billing.approved_success
|
||||
: t.client_billing.flagged_success;
|
||||
final UiSnackbarType type = state.message == 'approved'
|
||||
? UiSnackbarType.success
|
||||
: UiSnackbarType.warning;
|
||||
|
||||
UiSnackbar.show(context, message: message, type: type);
|
||||
Modular.to.toAwaitingApproval();
|
||||
} else if (state.status == ShiftCompletionReviewStatus.failure) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: state.errorMessage ?? t.errors.generic.unknown,
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, ShiftCompletionReviewState state) {
|
||||
final bool isLoading =
|
||||
state.status == ShiftCompletionReviewStatus.loading;
|
||||
|
||||
return Row(
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
@@ -21,7 +50,9 @@ class CompletionReviewActions extends StatelessWidget {
|
||||
child: UiButton.secondary(
|
||||
text: t.client_billing.actions.flag_review,
|
||||
leadingIcon: UiIcons.warning,
|
||||
onPressed: () => _showFlagDialog(context),
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () => _showFlagDialog(context, state),
|
||||
size: UiButtonSize.large,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
@@ -32,29 +63,33 @@ class CompletionReviewActions extends StatelessWidget {
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
text: t.client_billing.actions.approve_pay,
|
||||
leadingIcon: UiIcons.checkCircle,
|
||||
onPressed: () {
|
||||
Modular.get<BillingBloc>().add(BillingInvoiceApproved(invoiceId));
|
||||
Modular.to.pop();
|
||||
UiSnackbar.show(
|
||||
leadingIcon: isLoading ? null : UiIcons.checkCircle,
|
||||
isLoading: isLoading,
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () {
|
||||
BlocProvider.of<ShiftCompletionReviewBloc>(
|
||||
context,
|
||||
message: t.client_billing.approved_success,
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
).add(ShiftCompletionReviewApproved(invoiceId));
|
||||
},
|
||||
size: UiButtonSize.large,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showFlagDialog(BuildContext context) {
|
||||
void _showFlagDialog(BuildContext context, ShiftCompletionReviewState state) {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) => AlertDialog(
|
||||
title: Text(t.client_billing.flag_dialog.title),
|
||||
surfaceTintColor: Colors.white,
|
||||
backgroundColor: Colors.white,
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
@@ -70,15 +105,10 @@ class CompletionReviewActions extends StatelessWidget {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (controller.text.isNotEmpty) {
|
||||
Modular.get<BillingBloc>().add(
|
||||
BillingInvoiceDisputed(invoiceId, controller.text),
|
||||
);
|
||||
Modular.to.toClientBilling();
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: t.client_billing.flagged_success,
|
||||
type: UiSnackbarType.warning,
|
||||
BlocProvider.of<ShiftCompletionReviewBloc>(context).add(
|
||||
ShiftCompletionReviewDisputed(invoiceId, controller.text),
|
||||
);
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
},
|
||||
child: Text(t.client_billing.flag_dialog.button),
|
||||
|
||||
@@ -15,9 +15,9 @@ class CompletionReviewAmount extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(UiConstants.space6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF),
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: const Color(0xFFDBEAFE)),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
|
||||
Reference in New Issue
Block a user