diff --git a/lib/redux/invoice/invoice_selectors.dart b/lib/redux/invoice/invoice_selectors.dart index 7ef999862..742f1c893 100644 --- a/lib/redux/invoice/invoice_selectors.dart +++ b/lib/redux/invoice/invoice_selectors.dart @@ -146,6 +146,14 @@ List filteredInvoicesSelector( !invoiceListState.custom2Filters.contains(invoice.customValue2)) { return false; } + if (invoiceListState.custom3Filters.isNotEmpty && + !invoiceListState.custom3Filters.contains(invoice.customValue3)) { + return false; + } + if (invoiceListState.custom4Filters.isNotEmpty && + !invoiceListState.custom4Filters.contains(invoice.customValue4)) { + return false; + } return true; }).toList(); diff --git a/lib/redux/recurring_invoice/recurring_invoice_selectors.dart b/lib/redux/recurring_invoice/recurring_invoice_selectors.dart index dc728c585..f7d322d46 100644 --- a/lib/redux/recurring_invoice/recurring_invoice_selectors.dart +++ b/lib/redux/recurring_invoice/recurring_invoice_selectors.dart @@ -32,31 +32,55 @@ List filteredRecurringInvoicesSelector( BuiltMap recurringInvoiceMap, BuiltMap clientMap, BuiltList recurringInvoiceList, - ListUIState recurringInvoiceListState, + ListUIState invoiceListState, StaticState staticState, BuiltMap userMap, ) { final list = recurringInvoiceList.where((recurringInvoiceId) { - final recurringInvoice = recurringInvoiceMap[recurringInvoiceId]; - if (filterEntityId != null && recurringInvoice.id != filterEntityId) { - return false; - } else {} + final invoice = recurringInvoiceMap[recurringInvoiceId]; + final client = + clientMap[invoice.clientId] ?? ClientEntity(id: invoice.clientId); - if (!recurringInvoice - .matchesStates(recurringInvoiceListState.stateFilters)) { + if (!client.isActive && + !client.matchesEntityFilter(filterEntityType, filterEntityId)) { return false; } - if (recurringInvoiceListState.custom1Filters.isNotEmpty && - !recurringInvoiceListState.custom1Filters - .contains(recurringInvoice.customValue1)) { + + if (filterEntityType == EntityType.client && client.id != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.user && + invoice.assignedUserId != filterEntityId) { return false; } - if (recurringInvoiceListState.custom2Filters.isNotEmpty && - !recurringInvoiceListState.custom2Filters - .contains(recurringInvoice.customValue2)) { + + if (!invoice.matchesStates(invoiceListState.stateFilters)) { return false; } - return recurringInvoice.matchesFilter(recurringInvoiceListState.filter); + if (!invoice.matchesStatuses(invoiceListState.statusFilters)) { + return false; + } + if (!invoice.matchesFilter(invoiceListState.filter) && + !client.matchesFilter(invoiceListState.filter)) { + return false; + } + if (invoiceListState.custom1Filters.isNotEmpty && + !invoiceListState.custom1Filters.contains(invoice.customValue1)) { + return false; + } + if (invoiceListState.custom2Filters.isNotEmpty && + !invoiceListState.custom2Filters.contains(invoice.customValue2)) { + return false; + } + if (invoiceListState.custom3Filters.isNotEmpty && + !invoiceListState.custom3Filters.contains(invoice.customValue3)) { + return false; + } + if (invoiceListState.custom4Filters.isNotEmpty && + !invoiceListState.custom4Filters.contains(invoice.customValue4)) { + return false; + } + return true; + }).toList(); list.sort((recurringInvoiceAId, recurringInvoiceBId) { @@ -65,8 +89,8 @@ List filteredRecurringInvoicesSelector( return recurringInvoiceA.compareTo( invoice: recurringInvoiceB, - sortField: recurringInvoiceListState.sortField, - sortAscending: recurringInvoiceListState.sortAscending, + sortField: invoiceListState.sortField, + sortAscending: invoiceListState.sortAscending, clientMap: clientMap, staticState: staticState, userMap: userMap, @@ -76,8 +100,52 @@ List filteredRecurringInvoicesSelector( return list; } +var memoizedRecurringInvoiceStatsForClient = memo2( + (String clientId, BuiltMap invoiceMap) => + recurringInvoiceStatsForClient(clientId, invoiceMap)); + +EntityStats recurringInvoiceStatsForClient( + String clientId, BuiltMap invoiceMap) { + int countActive = 0; + int countArchived = 0; + invoiceMap.forEach((invoiceId, invoice) { + if (invoice.clientId == clientId) { + if (invoice.isActive) { + countActive++; + } else if (invoice.isArchived) { + countArchived++; + } + } + }); + + return EntityStats(countActive: countActive, countArchived: countArchived); +} + +var memoizedRecurringInvoiceStatsForUser = memo2( + (String userId, BuiltMap invoiceMap) => + recurringInvoiceStatsForUser(userId, invoiceMap)); + +EntityStats recurringInvoiceStatsForUser( + String userId, BuiltMap invoiceMap) { + int countActive = 0; + int countArchived = 0; + invoiceMap.forEach((invoiceId, invoice) { + if (invoice.assignedUserId == userId) { + if (invoice.isActive) { + countActive++; + } else if (invoice.isDeleted) { + countArchived++; + } + } + }); + + return EntityStats(countActive: countActive, countArchived: countArchived); +} + + bool hasRecurringInvoiceChanges(InvoiceEntity recurringInvoice, BuiltMap recurringInvoiceMap) => recurringInvoice.isNew ? recurringInvoice.isChanged : recurringInvoice != recurringInvoiceMap[recurringInvoice.id]; + diff --git a/lib/ui/client/view/client_view_overview.dart b/lib/ui/client/view/client_view_overview.dart index a93b88ec4..ba6680085 100644 --- a/lib/ui/client/view/client_view_overview.dart +++ b/lib/ui/client/view/client_view_overview.dart @@ -10,6 +10,7 @@ import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart'; import 'package:invoiceninja_flutter/redux/project/project_selectors.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; +import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart'; import 'package:invoiceninja_flutter/redux/task/task_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart'; @@ -176,6 +177,19 @@ class ClientOverview extends StatelessWidget { memoizedInvoiceStatsForClient(client.id, state.invoiceState.map) .present(localization.active, localization.archived), ), + if (company.isModuleEnabled(EntityType.recurringInvoice)) + EntitiesListTile( + isFilter: isFilter, + entityType: EntityType.recurringInvoice, + title: localization.recurringInvoices, + onTap: () => + viewModel.onEntityPressed(context, EntityType.recurringInvoice), + onLongPress: () => viewModel.onEntityPressed( + context, EntityType.recurringInvoice, true), + subtitle: memoizedRecurringInvoiceStatsForClient( + client.id, state.recurringInvoiceState.map) + .present(localization.active, localization.archived), + ), if (company.isModuleEnabled(EntityType.payment)) EntitiesListTile( isFilter: isFilter, diff --git a/lib/ui/user/view/user_view.dart b/lib/ui/user/view/user_view.dart index a91e153e5..9023e703c 100644 --- a/lib/ui/user/view/user_view.dart +++ b/lib/ui/user/view/user_view.dart @@ -4,6 +4,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/credit/credit_selectors.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; +import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/entity_header.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; @@ -44,47 +45,60 @@ class UserView extends StatelessWidget { secondValue: user.phone, ), ListDivider(), - EntitiesListTile( - isFilter: isFilter, - title: localization.invoices, - entityType: EntityType.invoice, - onTap: () => viewModel.onEntityPressed(context, EntityType.invoice), - onLongPress: () => - viewModel.onEntityPressed(context, EntityType.invoice, true), - subtitle: - memoizedInvoiceStatsForUser(user.id, state.invoiceState.map) - .present(localization.active, localization.archived), - ), - userCompany.canViewOrCreate(EntityType.quote) - ? EntitiesListTile( - isFilter: isFilter, - entityType: EntityType.quote, - title: localization.quotes, - onTap: () => - viewModel.onEntityPressed(context, EntityType.quote), - onLongPress: () => viewModel.onEntityPressed( - context, EntityType.quote, true), - subtitle: memoizedQuoteStatsForUser( - user.id, - state.quoteState.map, - ).present(localization.active, localization.archived), - ) - : Container(), - userCompany.canViewOrCreate(EntityType.credit) - ? EntitiesListTile( - isFilter: isFilter, - entityType: EntityType.credit, - title: localization.credits, - onTap: () => - viewModel.onEntityPressed(context, EntityType.credit), - onLongPress: () => viewModel.onEntityPressed( - context, EntityType.credit, true), - subtitle: memoizedCreditStatsForUser( - user.id, - state.creditState.map, - ).present(localization.active, localization.archived), - ) - : Container(), + if (userCompany.canViewOrCreate(EntityType.invoice)) + EntitiesListTile( + isFilter: isFilter, + title: localization.invoices, + entityType: EntityType.invoice, + onTap: () => + viewModel.onEntityPressed(context, EntityType.invoice), + onLongPress: () => + viewModel.onEntityPressed(context, EntityType.invoice, true), + subtitle: + memoizedInvoiceStatsForUser(user.id, state.invoiceState.map) + .present(localization.active, localization.archived), + ), + if (userCompany.canViewOrCreate(EntityType.recurringInvoice)) + EntitiesListTile( + isFilter: isFilter, + title: localization.recurringInvoices, + entityType: EntityType.recurringInvoice, + onTap: () => viewModel.onEntityPressed( + context, EntityType.recurringInvoice), + onLongPress: () => viewModel.onEntityPressed( + context, EntityType.recurringInvoice, true), + subtitle: memoizedRecurringInvoiceStatsForUser( + user.id, state.recurringInvoiceState.map) + .present(localization.active, localization.archived), + ), + if (userCompany.canViewOrCreate(EntityType.quote)) + EntitiesListTile( + isFilter: isFilter, + entityType: EntityType.quote, + title: localization.quotes, + onTap: () => viewModel.onEntityPressed(context, EntityType.quote), + onLongPress: () => + viewModel.onEntityPressed(context, EntityType.quote, true), + subtitle: memoizedQuoteStatsForUser( + user.id, + state.quoteState.map, + ).present(localization.active, localization.archived), + ), + if (userCompany.canViewOrCreate(EntityType.credit)) + EntitiesListTile( + isFilter: isFilter, + entityType: EntityType.credit, + title: localization.credits, + onTap: () => + viewModel.onEntityPressed(context, EntityType.credit), + onLongPress: () => + viewModel.onEntityPressed(context, EntityType.credit, true), + subtitle: memoizedCreditStatsForUser( + user.id, + state.creditState.map, + ).present(localization.active, localization.archived), + ), + /* userCompany.canViewOrCreate(EntityType.project) ? EntityListTile(