Payments
This commit is contained in:
parent
a81cb5891f
commit
d04386974b
|
|
@ -198,3 +198,10 @@ class FilterPaymentsByCustom2 implements PersistUI {
|
||||||
|
|
||||||
FilterPaymentsByCustom2(this.value);
|
FilterPaymentsByCustom2(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FilterPaymentsByClient implements PersistUI {
|
||||||
|
final int clientId;
|
||||||
|
|
||||||
|
FilterPaymentsByClient([this.clientId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,17 +68,27 @@ class ClientOverview extends StatelessWidget {
|
||||||
EntityListTile(
|
EntityListTile(
|
||||||
icon: FontAwesomeIcons.filePdfO,
|
icon: FontAwesomeIcons.filePdfO,
|
||||||
title: localization.invoices,
|
title: localization.invoices,
|
||||||
onTap: () => viewModel.onInvoicesPressed(context),
|
onTap: () => viewModel.onEntityPressed(context, EntityType.invoice),
|
||||||
subtitle: memoizedInvoiceStatsForClient(
|
subtitle: memoizedInvoiceStatsForClient(
|
||||||
client.id,
|
client.id,
|
||||||
state.invoiceState.map,
|
state.invoiceState.map,
|
||||||
localization.active,
|
localization.active,
|
||||||
localization.archived),
|
localization.archived),
|
||||||
),
|
),
|
||||||
|
EntityListTile(
|
||||||
|
icon: FontAwesomeIcons.creditCard,
|
||||||
|
title: localization.payments,
|
||||||
|
onTap: () => viewModel.onEntityPressed(context, EntityType.payment),
|
||||||
|
subtitle: memoizedQuoteStatsForClient(
|
||||||
|
client.id,
|
||||||
|
state.quoteState.map,
|
||||||
|
localization.active,
|
||||||
|
localization.archived),
|
||||||
|
),
|
||||||
company.isModuleEnabled(EntityType.quote) ? EntityListTile(
|
company.isModuleEnabled(EntityType.quote) ? EntityListTile(
|
||||||
icon: FontAwesomeIcons.fileAltO,
|
icon: FontAwesomeIcons.fileAltO,
|
||||||
title: localization.quotes,
|
title: localization.quotes,
|
||||||
onTap: () => viewModel.onQuotesPressed(context),
|
onTap: () => viewModel.onEntityPressed(context, EntityType.quote),
|
||||||
subtitle: memoizedQuoteStatsForClient(
|
subtitle: memoizedQuoteStatsForClient(
|
||||||
client.id,
|
client.id,
|
||||||
state.quoteState.map,
|
state.quoteState.map,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/client/client_screen.dart';
|
import 'package:invoiceninja_flutter/ui/client/client_screen.dart';
|
||||||
|
|
@ -42,8 +43,7 @@ class ClientViewVM {
|
||||||
final Function(BuildContext, EntityAction) onActionSelected;
|
final Function(BuildContext, EntityAction) onActionSelected;
|
||||||
final Function(BuildContext) onEditPressed;
|
final Function(BuildContext) onEditPressed;
|
||||||
final Function onBackPressed;
|
final Function onBackPressed;
|
||||||
final Function(BuildContext) onInvoicesPressed;
|
final Function(BuildContext, EntityType) onEntityPressed;
|
||||||
final Function(BuildContext) onQuotesPressed;
|
|
||||||
final Function(BuildContext, bool) onRefreshed;
|
final Function(BuildContext, bool) onRefreshed;
|
||||||
final bool isSaving;
|
final bool isSaving;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
@ -53,9 +53,8 @@ class ClientViewVM {
|
||||||
@required this.client,
|
@required this.client,
|
||||||
@required this.company,
|
@required this.company,
|
||||||
@required this.onActionSelected,
|
@required this.onActionSelected,
|
||||||
|
@required this.onEntityPressed,
|
||||||
@required this.onEditPressed,
|
@required this.onEditPressed,
|
||||||
@required this.onInvoicesPressed,
|
|
||||||
@required this.onQuotesPressed,
|
|
||||||
@required this.onBackPressed,
|
@required this.onBackPressed,
|
||||||
@required this.isSaving,
|
@required this.isSaving,
|
||||||
@required this.isLoading,
|
@required this.isLoading,
|
||||||
|
|
@ -94,13 +93,21 @@ class ClientViewVM {
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onInvoicesPressed: (BuildContext context) {
|
onEntityPressed: (BuildContext context, EntityType entityType) {
|
||||||
|
switch (entityType) {
|
||||||
|
case EntityType.invoice:
|
||||||
store.dispatch(FilterInvoicesByClient(client.id));
|
store.dispatch(FilterInvoicesByClient(client.id));
|
||||||
store.dispatch(ViewInvoiceList(context));
|
store.dispatch(ViewInvoiceList(context));
|
||||||
},
|
break;
|
||||||
onQuotesPressed: (BuildContext context) {
|
case EntityType.quote:
|
||||||
store.dispatch(FilterQuotesByClient(client.id));
|
store.dispatch(FilterQuotesByClient(client.id));
|
||||||
store.dispatch(ViewQuoteList(context));
|
store.dispatch(ViewQuoteList(context));
|
||||||
|
break;
|
||||||
|
case EntityType.payment:
|
||||||
|
store.dispatch(FilterPaymentsByClient(client.id));
|
||||||
|
store.dispatch(ViewPaymentList(context));
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRefreshed: (context, loadActivities) =>
|
onRefreshed: (context, loadActivities) =>
|
||||||
_handleRefresh(context, loadActivities),
|
_handleRefresh(context, loadActivities),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/payment_model.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
||||||
|
|
@ -15,32 +16,21 @@ class PaymentList extends StatelessWidget {
|
||||||
@required this.viewModel,
|
@required this.viewModel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
void _showMenu(
|
||||||
Widget build(BuildContext context) {
|
BuildContext context, PaymentEntity payment, ClientEntity client) async {
|
||||||
if (!viewModel.isLoaded) {
|
|
||||||
return LoadingIndicator();
|
|
||||||
} else if (viewModel.paymentList.isEmpty) {
|
|
||||||
return Opacity(
|
|
||||||
opacity: 0.5,
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
AppLocalization.of(context).noRecordsFound,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _buildListView(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showMenu(BuildContext context, PaymentEntity payment) async {
|
|
||||||
final user = viewModel.user;
|
final user = viewModel.user;
|
||||||
final message = await showDialog<String>(
|
final message = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
||||||
|
user.canEditEntity(payment) && client.hasEmailAddress
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.send),
|
||||||
|
title: Text(AppLocalization.of(context).email),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, payment, EntityAction.email),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
Divider(),
|
||||||
user.canEditEntity(payment) && !payment.isActive
|
user.canEditEntity(payment) && !payment.isActive
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.restore),
|
leading: Icon(Icons.restore),
|
||||||
|
|
@ -74,29 +64,96 @@ class PaymentList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView(BuildContext context) {
|
@override
|
||||||
return RefreshIndicator(
|
Widget build(BuildContext context) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final listState = viewModel.listState;
|
||||||
|
final filteredClientId = listState.filterClientId;
|
||||||
|
final filteredClient =
|
||||||
|
filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
filteredClient != null
|
||||||
|
? Material(
|
||||||
|
color: Colors.orangeAccent,
|
||||||
|
elevation: 6.0,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => viewModel.onViewClientFilterPressed(context),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(width: 18.0),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
localization.clientsPayments.replaceFirst(
|
||||||
|
':client', filteredClient.displayName),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: () => viewModel.onClearClientFilterPressed(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
Expanded(
|
||||||
|
child: !viewModel.isLoaded
|
||||||
|
? LoadingIndicator()
|
||||||
|
: RefreshIndicator(
|
||||||
onRefresh: () => viewModel.onRefreshed(context),
|
onRefresh: () => viewModel.onRefreshed(context),
|
||||||
child: ListView.builder(
|
child: viewModel.paymentList.isEmpty
|
||||||
|
? Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalization.of(context).noRecordsFound,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.paymentList.length,
|
itemCount: viewModel.paymentList.length,
|
||||||
itemBuilder: (BuildContext context, index) {
|
itemBuilder: (BuildContext context, index) {
|
||||||
final paymentId = viewModel.paymentList[index];
|
final paymentId = viewModel.paymentList[index];
|
||||||
final payment = viewModel.paymentMap[paymentId];
|
final payment = viewModel.paymentMap[paymentId];
|
||||||
return Column(children: <Widget>[
|
final client =
|
||||||
|
viewModel.clientMap[payment.clientId];
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
PaymentListItem(
|
PaymentListItem(
|
||||||
user: viewModel.user,
|
user: viewModel.user,
|
||||||
filter: viewModel.filter,
|
filter: viewModel.filter,
|
||||||
payment: payment,
|
payment: payment,
|
||||||
onDismissed: (DismissDirection direction) =>
|
onDismissed: (DismissDirection direction) =>
|
||||||
viewModel.onDismissed(context, payment, direction),
|
viewModel.onDismissed(
|
||||||
onTap: () => viewModel.onPaymentTap(context, payment),
|
context, payment, direction),
|
||||||
onLongPress: () => _showMenu(context, payment),
|
onTap: () =>
|
||||||
|
viewModel.onPaymentTap(context, payment),
|
||||||
|
onLongPress: () =>
|
||||||
|
_showMenu(context, payment, client),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -31,20 +33,25 @@ class PaymentListBuilder extends StatelessWidget {
|
||||||
|
|
||||||
class PaymentListVM {
|
class PaymentListVM {
|
||||||
final UserEntity user;
|
final UserEntity user;
|
||||||
|
final ListUIState listState;
|
||||||
final List<int> paymentList;
|
final List<int> paymentList;
|
||||||
final BuiltMap<int, PaymentEntity> paymentMap;
|
final BuiltMap<int, PaymentEntity> paymentMap;
|
||||||
|
final BuiltMap<int, ClientEntity> clientMap;
|
||||||
final String filter;
|
final String filter;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final bool isLoaded;
|
final bool isLoaded;
|
||||||
final Function(BuildContext, PaymentEntity) onPaymentTap;
|
final Function(BuildContext, PaymentEntity) onPaymentTap;
|
||||||
final Function(BuildContext, PaymentEntity, DismissDirection) onDismissed;
|
final Function(BuildContext, PaymentEntity, DismissDirection) onDismissed;
|
||||||
final Function(BuildContext) onRefreshed;
|
final Function(BuildContext) onRefreshed;
|
||||||
|
final Function onClearClientFilterPressed;
|
||||||
|
final Function(BuildContext) onViewClientFilterPressed;
|
||||||
final Function(BuildContext, PaymentEntity, EntityAction) onEntityAction;
|
final Function(BuildContext, PaymentEntity, EntityAction) onEntityAction;
|
||||||
|
|
||||||
PaymentListVM({
|
PaymentListVM({
|
||||||
@required this.user,
|
@required this.user,
|
||||||
@required this.paymentList,
|
@required this.paymentList,
|
||||||
@required this.paymentMap,
|
@required this.paymentMap,
|
||||||
|
@required this.clientMap,
|
||||||
@required this.filter,
|
@required this.filter,
|
||||||
@required this.isLoading,
|
@required this.isLoading,
|
||||||
@required this.isLoaded,
|
@required this.isLoaded,
|
||||||
|
|
@ -52,6 +59,9 @@ class PaymentListVM {
|
||||||
@required this.onDismissed,
|
@required this.onDismissed,
|
||||||
@required this.onRefreshed,
|
@required this.onRefreshed,
|
||||||
@required this.onEntityAction,
|
@required this.onEntityAction,
|
||||||
|
@required this.onClearClientFilterPressed,
|
||||||
|
@required this.onViewClientFilterPressed,
|
||||||
|
@required this.listState,
|
||||||
});
|
});
|
||||||
|
|
||||||
static PaymentListVM fromStore(Store<AppState> store) {
|
static PaymentListVM fromStore(Store<AppState> store) {
|
||||||
|
|
@ -72,9 +82,11 @@ class PaymentListVM {
|
||||||
paymentList: memoizedFilteredPaymentList(state.paymentState.map,
|
paymentList: memoizedFilteredPaymentList(state.paymentState.map,
|
||||||
state.paymentState.list, state.paymentListState),
|
state.paymentState.list, state.paymentListState),
|
||||||
paymentMap: state.paymentState.map,
|
paymentMap: state.paymentState.map,
|
||||||
|
clientMap: state.clientState.map,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
isLoaded: state.paymentState.isLoaded,
|
isLoaded: state.paymentState.isLoaded,
|
||||||
filter: state.paymentUIState.listUIState.filter,
|
filter: state.paymentUIState.listUIState.filter,
|
||||||
|
listState: state.paymentListState,
|
||||||
onPaymentTap: (context, payment) {
|
onPaymentTap: (context, payment) {
|
||||||
store.dispatch(ViewPayment(paymentId: payment.id, context: context));
|
store.dispatch(ViewPayment(paymentId: payment.id, context: context));
|
||||||
},
|
},
|
||||||
|
|
@ -100,6 +112,12 @@ class PaymentListVM {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onClearClientFilterPressed: () =>
|
||||||
|
store.dispatch(FilterPaymentsByClient()),
|
||||||
|
onViewClientFilterPressed: (BuildContext context) => store.dispatch(
|
||||||
|
ViewClient(
|
||||||
|
clientId: state.paymentListState.filterClientId,
|
||||||
|
context: context)),
|
||||||
onRefreshed: (context) => _handleRefresh(context),
|
onRefreshed: (context) => _handleRefresh(context),
|
||||||
onDismissed: (BuildContext context, PaymentEntity payment,
|
onDismissed: (BuildContext context, PaymentEntity payment,
|
||||||
DismissDirection direction) {
|
DismissDirection direction) {
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ class AppLocalization {
|
||||||
'please_enter_an_invoice_number': 'Please enter an invoice number',
|
'please_enter_an_invoice_number': 'Please enter an invoice number',
|
||||||
'please_enter_a_quote_number': 'Please enter a quote number',
|
'please_enter_a_quote_number': 'Please enter a quote number',
|
||||||
'clients_invoices': ':client\'s invoices',
|
'clients_invoices': ':client\'s invoices',
|
||||||
|
'clients_payments': ':client\'s payments',
|
||||||
'past_due': 'Past Due',
|
'past_due': 'Past Due',
|
||||||
'draft': 'Draft',
|
'draft': 'Draft',
|
||||||
'sent': 'Sent',
|
'sent': 'Sent',
|
||||||
|
|
@ -7418,6 +7419,9 @@ class AppLocalization {
|
||||||
String get clientsInvoices =>
|
String get clientsInvoices =>
|
||||||
_localizedValues[locale.languageCode]['clients_invoices'];
|
_localizedValues[locale.languageCode]['clients_invoices'];
|
||||||
|
|
||||||
|
String get clientsPayments =>
|
||||||
|
_localizedValues[locale.languageCode]['clients_payments'];
|
||||||
|
|
||||||
String get pastDue => _localizedValues[locale.languageCode]['past_due'];
|
String get pastDue => _localizedValues[locale.languageCode]['past_due'];
|
||||||
|
|
||||||
String get draft => _localizedValues[locale.languageCode]['draft'];
|
String get draft => _localizedValues[locale.languageCode]['draft'];
|
||||||
|
|
|
||||||
|
|
@ -198,3 +198,9 @@ class FilterStubsByCustom2 implements PersistUI {
|
||||||
|
|
||||||
FilterStubsByCustom2(this.value);
|
FilterStubsByCustom2(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FilterStubsByClient implements PersistUI {
|
||||||
|
final int clientId;
|
||||||
|
|
||||||
|
FilterStubsByClient([this.clientId]);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue