diff --git a/bump_version.sh b/bump_version.sh index db5c8f8765a..ad6f4c2eda4 100644 --- a/bump_version.sh +++ b/bump_version.sh @@ -1,17 +1,17 @@ #!/bin/bash current_version=$(grep "version: 5.0." pubspec.yaml | cut -f2 -d "+" ) -new_vesion=$((current_version+1)) +new_version=$((current_version+1)) date_today=$(date +%F) -echo "Bump version... $current_version => $new_vesion" +echo "Bump version... $current_version => $new_version" -sed -i -e "s/version: 5.0.$current_version+$current_version/version: 5.0.$new_vesion+$new_vesion/g" ./pubspec.yaml -sed -i -e "s/version: 5.0.$current_version+$current_version/version: 5.0.$new_vesion+$new_vesion/g" ./pubspec.foss.yaml -sed -i -e "s/v5.0.$current_version/v5.0.$new_vesion/g" ./.github/workflows/flatpak.yml -sed -i -e 's//\n /g' ./flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml -sed -i -e "s/kClientVersion = '5.0.$current_version'/kClientVersion = '5.0.$new_vesion'/g" ./lib/constants.dart -sed -i -e "s/version: '5.0.$current_version'/version: '5.0.$new_vesion'/g" ./snap/snapcraft.yaml +sed -i -e "s/version: 5.0.$current_version+$current_version/version: 5.0.$new_version+$new_version/g" ./pubspec.yaml +sed -i -e "s/version: 5.0.$current_version+$current_version/version: 5.0.$new_version+$new_version/g" ./pubspec.foss.yaml +sed -i -e "s/v5.0.$current_version/v5.0.$new_version/g" ./.github/workflows/flatpak.yml +sed -i -e 's//\n /g' ./flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml +sed -i -e "s/kClientVersion = '5.0.$current_version'/kClientVersion = '5.0.$new_version'/g" ./lib/constants.dart +sed -i -e "s/version: '5.0.$current_version'/version: '5.0.$new_version'/g" ./snap/snapcraft.yaml rm lib/flutter_version.dart echo "const FLUTTER_VERSION = const " > lib/flutter_version.dart diff --git a/lib/constants.dart b/lib/constants.dart index 0cf67172017..c9872a779fa 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1128,3 +1128,4 @@ const String kActivityAcceptPurchaseOrder = '137'; const String kActivityEmailPayment = '138'; const String kActivityExpenseNotificationSent = '139'; const String kActivityStatementSent = '140'; +const String kActivityComment = '141'; diff --git a/lib/data/models/client_model.dart b/lib/data/models/client_model.dart index c38a75b5d06..14b75427c0a 100644 --- a/lib/data/models/client_model.dart +++ b/lib/data/models/client_model.dart @@ -683,6 +683,10 @@ abstract class ClientEntity extends Object store.state.designState.map, entityType)) { actions.add(EntityAction.runTemplate); } + + if (!multiselect) { + actions.add(EntityAction.addComment); + } } if (!isDeleted! && multiselect) { diff --git a/lib/data/models/entities.dart b/lib/data/models/entities.dart index 193211aa61c..a5dbf8e28a1 100644 --- a/lib/data/models/entities.dart +++ b/lib/data/models/entities.dart @@ -767,7 +767,39 @@ abstract class ActivityEntity InvoiceHistoryEntity? get history; + // TODO remove isEmpty check + bool get isComment => + activityTypeId == kActivityComment || activityTypeId.isEmpty; + EntityType? get entityType { + if (isComment) { + if ((invoiceId ?? '').isNotEmpty) { + return EntityType.invoice; + } else if ((quoteId ?? '').isNotEmpty) { + return EntityType.quote; + } else if ((creditId ?? '').isNotEmpty) { + return EntityType.credit; + } else if ((recurringInvoiceId ?? '').isNotEmpty) { + return EntityType.recurringInvoice; + } else if ((paymentId ?? '').isNotEmpty) { + return EntityType.payment; + } else if ((projectId ?? '').isNotEmpty) { + return EntityType.project; + } else if ((taskId ?? '').isNotEmpty) { + return EntityType.task; + } else if ((expenseId ?? '').isNotEmpty) { + return EntityType.expense; + } else if ((recurringExpenseId ?? '').isNotEmpty) { + return EntityType.recurringExpense; + } else if ((purchaseOrderId ?? '').isNotEmpty) { + return EntityType.purchaseOrder; + } else if ((clientId ?? '').isNotEmpty) { + return EntityType.client; + } else if ((vendorId ?? '').isNotEmpty) { + return EntityType.vendor; + } + } + if ([ kActivityCreateClient, kActivityUpdateClient, diff --git a/lib/data/models/expense_model.dart b/lib/data/models/expense_model.dart index ef54f70806c..1e2c683ddda 100644 --- a/lib/data/models/expense_model.dart +++ b/lib/data/models/expense_model.dart @@ -373,6 +373,10 @@ abstract class ExpenseEntity extends Object actions.add(EntityAction.documents); } + if (!isDeleted! && !multiselect) { + //actions.add(EntityAction.addComment); + } + final superActions = super.getActions(userCompany: userCompany); if (actions.isNotEmpty && superActions.isNotEmpty && actions.last != null) { diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index c2668f5cf79..2a8f99affe4 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -1080,6 +1080,10 @@ abstract class InvoiceEntity extends Object store.state.designState.map, entityType!)) { actions.add(EntityAction.runTemplate); } + + if (!multiselect) { + actions.add(EntityAction.addComment); + } } if (userCompany.canEditEntity(this) && !isCancelledOrReversed) { diff --git a/lib/data/models/models.dart b/lib/data/models/models.dart index 14405c7fb4c..8f66fb9a273 100644 --- a/lib/data/models/models.dart +++ b/lib/data/models/models.dart @@ -141,6 +141,7 @@ class EntityAction extends EnumClass { static const EntityAction runTemplate = _$runTemplate; static const EntityAction bulkUpdate = _$bulkUpdate; static const EntityAction reconnect = _$reconnect; + static const EntityAction addComment = _$addComment; @override String toString() { diff --git a/lib/data/models/models.g.dart b/lib/data/models/models.g.dart index 25fd3f485a0..ed62581ad5c 100644 --- a/lib/data/models/models.g.dart +++ b/lib/data/models/models.g.dart @@ -106,6 +106,7 @@ const EntityAction _$unlink = const EntityAction._('unlink'); const EntityAction _$runTemplate = const EntityAction._('runTemplate'); const EntityAction _$bulkUpdate = const EntityAction._('bulkUpdate'); const EntityAction _$reconnect = const EntityAction._('reconnect'); +const EntityAction _$addComment = const EntityAction._('addComment'); EntityAction _$valueOf(String name) { switch (name) { @@ -287,6 +288,8 @@ EntityAction _$valueOf(String name) { return _$bulkUpdate; case 'reconnect': return _$reconnect; + case 'addComment': + return _$addComment; default: throw new ArgumentError(name); } @@ -383,6 +386,7 @@ final BuiltSet _$values = _$runTemplate, _$bulkUpdate, _$reconnect, + _$addComment, ]); Serializer _$entityActionSerializer = diff --git a/lib/data/models/payment_model.dart b/lib/data/models/payment_model.dart index a052370d8c1..79caca5c8d4 100644 --- a/lib/data/models/payment_model.dart +++ b/lib/data/models/payment_model.dart @@ -457,6 +457,10 @@ abstract class PaymentEntity extends Object store.state.designState.map, entityType)) { actions.add(EntityAction.runTemplate); } + + if (!multiselect) { + //actions.add(EntityAction.addComment); + } } if (!isDeleted! && multiselect) { diff --git a/lib/data/models/project_model.dart b/lib/data/models/project_model.dart index c7b1989e28f..0e03420437b 100644 --- a/lib/data/models/project_model.dart +++ b/lib/data/models/project_model.dart @@ -211,6 +211,10 @@ abstract class ProjectEntity extends Object store.state.designState.map, entityType)) { actions.add(EntityAction.runTemplate); } + + if (!multiselect) { + //actions.add(EntityAction.addComment); + } } if (actions.isNotEmpty && actions.last != null) { diff --git a/lib/data/models/task_model.dart b/lib/data/models/task_model.dart index 2be989b25fa..803231a3984 100644 --- a/lib/data/models/task_model.dart +++ b/lib/data/models/task_model.dart @@ -699,6 +699,10 @@ abstract class TaskEntity extends Object store.state.designState.map, entityType)) { actions.add(EntityAction.runTemplate); } + + if (!multiselect) { + //actions.add(EntityAction.addComment); + } } if (actions.isNotEmpty && actions.last != null) { diff --git a/lib/data/models/vendor_model.dart b/lib/data/models/vendor_model.dart index d6256a4c60c..0e0d971e516 100644 --- a/lib/data/models/vendor_model.dart +++ b/lib/data/models/vendor_model.dart @@ -269,6 +269,10 @@ abstract class VendorEntity extends Object actions.add(EntityAction.documents); } + if (!isDeleted! && !multiselect) { + actions.add(EntityAction.addComment); + } + if (actions.isNotEmpty && actions.last != null) { actions.add(null); } diff --git a/lib/redux/client/client_actions.dart b/lib/redux/client/client_actions.dart index e5a9d142e90..55378513207 100644 --- a/lib/redux/client/client_actions.dart +++ b/lib/redux/client/client_actions.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; import 'package:invoiceninja_flutter/ui/app/forms/client_picker.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart'; @@ -560,6 +561,16 @@ void handleClientAction(BuildContext? context, List clients, ), ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.client, + entityId: client.id, + ), + ); + break; default: print('## Error: action $action not handled in client_actions'); } diff --git a/lib/redux/credit/credit_actions.dart b/lib/redux/credit/credit_actions.dart index 0f33815d18f..868c244a8d0 100644 --- a/lib/redux/credit/credit_actions.dart +++ b/lib/redux/credit/credit_actions.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/utils/files.dart'; @@ -754,6 +755,16 @@ Future handleCreditAction(BuildContext context, List credits, ); } break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.credit, + entityId: credit.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in credit_actions'); break; diff --git a/lib/redux/expense/expense_actions.dart b/lib/redux/expense/expense_actions.dart index 9834137ec0d..2861bcf00b5 100644 --- a/lib/redux/expense/expense_actions.dart +++ b/lib/redux/expense/expense_actions.dart @@ -3,7 +3,6 @@ import 'dart:async'; // Flutter imports: import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; // Package imports: import 'package:built_collection/built_collection.dart'; @@ -12,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -397,6 +397,16 @@ void handleExpenseAction( ); } break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.expense, + entityId: expense.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in expense_actions'); break; diff --git a/lib/redux/invoice/invoice_actions.dart b/lib/redux/invoice/invoice_actions.dart index e94382adf64..5ee6ba5ea07 100644 --- a/lib/redux/invoice/invoice_actions.dart +++ b/lib/redux/invoice/invoice_actions.dart @@ -883,6 +883,16 @@ void handleInvoiceAction(BuildContext? context, List invoices, ), ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.invoice, + entityId: invoice.id, + ), + ); + break; case EntityAction.more: showEntityActionsDialog( entities: [invoice], diff --git a/lib/redux/payment/payment_actions.dart b/lib/redux/payment/payment_actions.dart index b3ebd392a32..c0a7ed6aca8 100644 --- a/lib/redux/payment/payment_actions.dart +++ b/lib/redux/payment/payment_actions.dart @@ -11,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -469,6 +470,16 @@ void handlePaymentAction( ), ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.payment, + entityId: payment.id, + ), + ); + break; default: print('## Error: action $action not handled in client_actions'); } diff --git a/lib/redux/project/project_actions.dart b/lib/redux/project/project_actions.dart index 33f96ddac64..1912e3c42e6 100644 --- a/lib/redux/project/project_actions.dart +++ b/lib/redux/project/project_actions.dart @@ -11,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -399,6 +400,16 @@ void handleProjectAction( ), ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.project, + entityId: project.id, + ), + ); + break; default: print('## Error: action $action not handled in project_actions'); } diff --git a/lib/redux/purchase_order/purchase_order_actions.dart b/lib/redux/purchase_order/purchase_order_actions.dart index 87a0b5807b8..64ead888cb9 100644 --- a/lib/redux/purchase_order/purchase_order_actions.dart +++ b/lib/redux/purchase_order/purchase_order_actions.dart @@ -468,7 +468,10 @@ class RemovePurchaseOrderContact implements PersistUI { } class AddPurchaseOrderItem implements PersistUI { - AddPurchaseOrderItem({this.purchaseOrderItem, this.index,}); + AddPurchaseOrderItem({ + this.purchaseOrderItem, + this.index, + }); final int? index; final InvoiceItemEntity? purchaseOrderItem; @@ -876,6 +879,16 @@ void handlePurchaseOrderAction(BuildContext? context, entities: [purchaseOrder], ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.purchaseOrder, + entityId: purchaseOrder.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in purchase_order_actions'); break; diff --git a/lib/redux/quote/quote_actions.dart b/lib/redux/quote/quote_actions.dart index 74c8203dc01..d22ec1f98dc 100644 --- a/lib/redux/quote/quote_actions.dart +++ b/lib/redux/quote/quote_actions.dart @@ -10,6 +10,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/utils/files.dart'; @@ -808,6 +809,16 @@ Future handleQuoteAction( ); } break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.quote, + entityId: quote.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in quote_actions'); break; diff --git a/lib/redux/recurring_expense/recurring_expense_actions.dart b/lib/redux/recurring_expense/recurring_expense_actions.dart index 6d4b94edcc1..a4e09ddce60 100644 --- a/lib/redux/recurring_expense/recurring_expense_actions.dart +++ b/lib/redux/recurring_expense/recurring_expense_actions.dart @@ -2,7 +2,7 @@ import 'dart:async'; // Flutter imports: -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; // Package imports: import 'package:built_collection/built_collection.dart'; @@ -11,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -459,6 +460,16 @@ void handleRecurringExpenseAction(BuildContext? context, ); } break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.recurringExpense, + entityId: recurringExpense.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in recurring_expense_actions'); break; diff --git a/lib/redux/recurring_invoice/recurring_invoice_actions.dart b/lib/redux/recurring_invoice/recurring_invoice_actions.dart index ff4179c11cf..8ce147a42ec 100644 --- a/lib/redux/recurring_invoice/recurring_invoice_actions.dart +++ b/lib/redux/recurring_invoice/recurring_invoice_actions.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; @@ -200,7 +201,10 @@ class AddRecurringInvoiceSuccess implements StopSaving, PersistData, PersistUI { } class AddRecurringInvoiceItem implements PersistUI { - AddRecurringInvoiceItem({this.invoiceItem, this.index,}); + AddRecurringInvoiceItem({ + this.invoiceItem, + this.index, + }); final int? index; final InvoiceItemEntity? invoiceItem; @@ -723,6 +727,19 @@ void handleRecurringInvoiceAction(BuildContext? context, recurringInvoiceIds, )); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.recurringInvoice, + entityId: recurringInvoice.id, + ), + ); + break; + default: + print( + '## Error: action $action not handled in recurring_invoice_actions'); } } diff --git a/lib/redux/task/task_actions.dart b/lib/redux/task/task_actions.dart index 65b441cefe6..68c46636f6c 100644 --- a/lib/redux/task/task_actions.dart +++ b/lib/redux/task/task_actions.dart @@ -11,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -553,6 +554,16 @@ void handleTaskAction( ), ); break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.task, + entityId: task.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in task_actions'); break; diff --git a/lib/redux/vendor/vendor_actions.dart b/lib/redux/vendor/vendor_actions.dart index b0b865fe7f1..3daacfa3649 100644 --- a/lib/redux/vendor/vendor_actions.dart +++ b/lib/redux/vendor/vendor_actions.dart @@ -2,7 +2,7 @@ import 'dart:async'; // Flutter imports: -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; // Package imports: import 'package:built_collection/built_collection.dart'; @@ -11,6 +11,7 @@ import 'package:http/http.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; @@ -395,6 +396,16 @@ void handleVendorAction( ); } break; + case EntityAction.addComment: + showDialog( + context: navigatorKey.currentContext!, + barrierDismissible: false, + builder: (context) => AddCommentDialog( + entityType: EntityType.vendor, + entityId: vendor.id, + ), + ); + break; default: print('## ERROR: unhandled action $action in vendor_actions'); break; diff --git a/lib/ui/app/lists/activity_list_tile.dart b/lib/ui/app/lists/activity_list_tile.dart index 6bb74fcb140..d2cbf5a1009 100644 --- a/lib/ui/app/lists/activity_list_tile.dart +++ b/lib/ui/app/lists/activity_list_tile.dart @@ -13,6 +13,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/icons.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class ActivityListTile extends StatelessWidget { const ActivityListTile({ @@ -70,8 +71,14 @@ class ActivityListTile extends StatelessWidget { ); return ListTile( - leading: Icon(getEntityIcon(activity.entityType)), - title: Text(title), + leading: Icon(activity.isComment + ? MdiIcons.comment + : getEntityIcon(activity.entityType)), + title: Text(activity.isComment + ? (user?.fullName == null + ? '' + : (user!.fullName + ': ' + activity.notes.replaceAll('\n', ' '))) + : title), onTap: !enableNavigation ? null : () { @@ -139,7 +146,7 @@ class ActivityListTile extends StatelessWidget { subtitle: Row( children: [ Flexible( - child: Text((activity.notes.isNotEmpty + child: Text((!activity.isComment && activity.notes.isNotEmpty ? localization.lookup(activity.notes).trim() + '\n' : '') + formatDate( diff --git a/lib/ui/reports/profit_loss_report.dart b/lib/ui/reports/profit_loss_report.dart index a0d70811480..62955602904 100644 --- a/lib/ui/reports/profit_loss_report.dart +++ b/lib/ui/reports/profit_loss_report.dart @@ -294,7 +294,7 @@ ReportResult profitAndLossReport( .lookup(expense.entityState); break; case ProfitAndLossReportFields.converted_amount: - value = expense.convertedAmount; + value = -expense.convertedAmount; break; } diff --git a/lib/ui/settings/invoice_design.dart b/lib/ui/settings/invoice_design.dart index 2d545a5c4ab..5f42d35218e 100644 --- a/lib/ui/settings/invoice_design.dart +++ b/lib/ui/settings/invoice_design.dart @@ -916,7 +916,7 @@ class _InvoiceDesignState extends State QuoteFields.date, QuoteFields.validUntil, QuoteFields.total, - if (false) QuoteFields.project, + QuoteFields.project, QuoteFields.customValue1, QuoteFields.customValue2, QuoteFields.customValue3, diff --git a/lib/utils/dialogs.dart b/lib/utils/dialogs.dart index 522066fa441..b8c28df611b 100644 --- a/lib/utils/dialogs.dart +++ b/lib/utils/dialogs.dart @@ -992,3 +992,90 @@ class _RunTemplateDialogState extends State { ); } } + +class AddCommentDialog extends StatefulWidget { + const AddCommentDialog({ + super.key, + required this.entityId, + required this.entityType, + }); + + final String entityId; + final EntityType entityType; + + @override + State createState() => _AddCommentDialogState(); +} + +class _AddCommentDialogState extends State { + String _comment = ''; + bool _isLoading = false; + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context)!; + final store = StoreProvider.of(context); + final state = store.state; + + return AlertDialog( + title: Text(localization.addComment), + actions: _isLoading + ? [] + : [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + localization.cancel.toUpperCase(), + ), + ), + TextButton( + onPressed: _comment.isEmpty + ? null + : () { + final credentials = state.credentials; + final url = '${credentials.url}/activities/notes'; + final data = { + 'entity': widget.entityType.pluralApiValue, + 'entity_id': widget.entityId, + 'notes': _comment.trim(), + }; + + print('DATA: $data'); + setState(() => _isLoading = true); + + WebClient() + .post(url, credentials.token, + data: jsonEncode(data)) + .then((response) async { + Navigator.of(navigatorKey.currentContext!).pop(); + showToast(localization.addedComment); + }).catchError((error) { + showErrorDialog(message: error); + setState(() => _isLoading = false); + }); + }, + child: Text( + localization.save.toUpperCase(), + ), + ), + ], + content: _isLoading + ? LinearProgressIndicator() + : DecoratedFormField( + label: localization.comment, + keyboardType: TextInputType.multiline, + initialValue: _comment, + onChanged: (value) { + setState(() { + _comment = value.trim(); + }); + }, + minLines: 6, + maxLines: 6, + autofocus: true, + ), + ); + } +} diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 5ef026c4681..2e2378acd12 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,9 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'comment': 'Comment', + 'add_comment': 'Add Comment', + 'added_comment': 'Successfully saved comment', 'disconnected': 'Disconnected', 'reconnect': 'Reconnect', 'e_invoice_settings': 'E-Invoice Settings', @@ -115515,6 +115518,18 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['disconnected'] ?? _localizedValues['en']!['disconnected']!; + String get addComment => + _localizedValues[localeCode]!['add_comment'] ?? + _localizedValues['en']!['add_comment']!; + + String get comment => + _localizedValues[localeCode]!['comment'] ?? + _localizedValues['en']!['comment']!; + + String get addedComment => + _localizedValues[localeCode]!['added_comment'] ?? + _localizedValues['en']!['added_comment']!; + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) { diff --git a/lib/utils/icons.dart b/lib/utils/icons.dart index c994b819fbf..9db63618dca 100644 --- a/lib/utils/icons.dart +++ b/lib/utils/icons.dart @@ -138,6 +138,8 @@ IconData? getEntityActionIcon(EntityAction? entityAction) { return MdiIcons.arrowRightCircleOutline; case EntityAction.bulkUpdate: return MdiIcons.squareEditOutline; + case EntityAction.addComment: + return MdiIcons.comment; default: return null; }