Merge pull request #293 from Oloodi/billing_screen

Billing screen
This commit is contained in:
Achintha Isuru
2026-01-26 19:00:28 -05:00
committed by GitHub
33 changed files with 19743 additions and 18754 deletions

View File

@@ -1,16 +1,16 @@
# Basic Usage
```dart
ExampleConnector.instance.createWorkforce(createWorkforceVariables).execute();
ExampleConnector.instance.updateWorkforce(updateWorkforceVariables).execute();
ExampleConnector.instance.deactivateWorkforce(deactivateWorkforceVariables).execute();
ExampleConnector.instance.listInvoices(listInvoicesVariables).execute();
ExampleConnector.instance.getInvoiceById(getInvoiceByIdVariables).execute();
ExampleConnector.instance.listInvoicesByVendorId(listInvoicesByVendorIdVariables).execute();
ExampleConnector.instance.listInvoicesByBusinessId(listInvoicesByBusinessIdVariables).execute();
ExampleConnector.instance.listInvoicesByOrderId(listInvoicesByOrderIdVariables).execute();
ExampleConnector.instance.listInvoicesByStatus(listInvoicesByStatusVariables).execute();
ExampleConnector.instance.filterInvoices(filterInvoicesVariables).execute();
ExampleConnector.instance.createBusiness(createBusinessVariables).execute();
ExampleConnector.instance.updateBusiness(updateBusinessVariables).execute();
ExampleConnector.instance.deleteBusiness(deleteBusinessVariables).execute();
ExampleConnector.instance.listCustomRateCards().execute();
ExampleConnector.instance.getCustomRateCardById(getCustomRateCardByIdVariables).execute();
ExampleConnector.instance.listClientFeedbacks(listClientFeedbacksVariables).execute();
ExampleConnector.instance.getClientFeedbackById(getClientFeedbackByIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByBusinessId(listClientFeedbacksByBusinessIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByVendorId(listClientFeedbacksByVendorIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByBusinessAndVendor(listClientFeedbacksByBusinessAndVendorVariables).execute();
```
@@ -23,8 +23,8 @@ Optional fields can be discovered based on classes that have `Optional` object t
This is an example of a mutation with an optional field:
```dart
await ExampleConnector.instance.updateVendor({ ... })
.companyName(...)
await ExampleConnector.instance.updateActivityLog({ ... })
.userId(...)
.execute();
```

View File

@@ -8,6 +8,7 @@ class CreateAccountVariablesBuilder {
String ownerId;
Optional<String> _accountNumber = Optional.optional(nativeFromJson, nativeToJson);
Optional<String> _routeNumber = Optional.optional(nativeFromJson, nativeToJson);
Optional<Timestamp> _expiryTime = Optional.optional((json) => json['expiryTime'] = Timestamp.fromJson(json['expiryTime']), defaultSerializer);
final FirebaseDataConnect _dataConnect; CreateAccountVariablesBuilder isPrimary(bool? t) {
_isPrimary.value = t;
@@ -21,6 +22,10 @@ class CreateAccountVariablesBuilder {
_routeNumber.value = t;
return this;
}
CreateAccountVariablesBuilder expiryTime(Timestamp? t) {
_expiryTime.value = t;
return this;
}
CreateAccountVariablesBuilder(this._dataConnect, {required this.bank,required this.type,required this.last4,required this.ownerId,});
Deserializer<CreateAccountData> dataDeserializer = (dynamic json) => CreateAccountData.fromJson(jsonDecode(json));
@@ -30,7 +35,7 @@ class CreateAccountVariablesBuilder {
}
MutationRef<CreateAccountData, CreateAccountVariables> ref() {
CreateAccountVariables vars= CreateAccountVariables(bank: bank,type: type,last4: last4,isPrimary: _isPrimary,ownerId: ownerId,accountNumber: _accountNumber,routeNumber: _routeNumber,);
CreateAccountVariables vars= CreateAccountVariables(bank: bank,type: type,last4: last4,isPrimary: _isPrimary,ownerId: ownerId,accountNumber: _accountNumber,routeNumber: _routeNumber,expiryTime: _expiryTime,);
return _dataConnect.mutation("createAccount", dataDeserializer, varsSerializer, vars);
}
}
@@ -112,6 +117,7 @@ class CreateAccountVariables {
final String ownerId;
late final Optional<String>accountNumber;
late final Optional<String>routeNumber;
late final Optional<Timestamp>expiryTime;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
CreateAccountVariables.fromJson(Map<String, dynamic> json):
@@ -136,6 +142,10 @@ class CreateAccountVariables {
routeNumber = Optional.optional(nativeFromJson, nativeToJson);
routeNumber.value = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']);
expiryTime = Optional.optional((json) => json['expiryTime'] = Timestamp.fromJson(json['expiryTime']), defaultSerializer);
expiryTime.value = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']);
}
@override
bool operator ==(Object other) {
@@ -153,11 +163,12 @@ class CreateAccountVariables {
isPrimary == otherTyped.isPrimary &&
ownerId == otherTyped.ownerId &&
accountNumber == otherTyped.accountNumber &&
routeNumber == otherTyped.routeNumber;
routeNumber == otherTyped.routeNumber &&
expiryTime == otherTyped.expiryTime;
}
@override
int get hashCode => Object.hashAll([bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode]);
int get hashCode => Object.hashAll([bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, expiryTime.hashCode]);
Map<String, dynamic> toJson() {
@@ -177,6 +188,9 @@ class CreateAccountVariables {
if(routeNumber.state == OptionalState.set) {
json['routeNumber'] = routeNumber.toJson();
}
if(expiryTime.state == OptionalState.set) {
json['expiryTime'] = expiryTime.toJson();
}
return json;
}
@@ -188,6 +202,7 @@ class CreateAccountVariables {
required this.ownerId,
required this.accountNumber,
required this.routeNumber,
required this.expiryTime,
});
}

View File

@@ -46,6 +46,7 @@ class FilterAccountsAccounts {
final bool? isPrimary;
final String ownerId;
final String? accountNumber;
final Timestamp? expiryTime;
final String? routeNumber;
FilterAccountsAccounts.fromJson(dynamic json):
@@ -56,6 +57,7 @@ class FilterAccountsAccounts {
isPrimary = json['isPrimary'] == null ? null : nativeFromJson<bool>(json['isPrimary']),
ownerId = nativeFromJson<String>(json['ownerId']),
accountNumber = json['accountNumber'] == null ? null : nativeFromJson<String>(json['accountNumber']),
expiryTime = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']),
routeNumber = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']);
@override
bool operator ==(Object other) {
@@ -74,11 +76,12 @@ class FilterAccountsAccounts {
isPrimary == otherTyped.isPrimary &&
ownerId == otherTyped.ownerId &&
accountNumber == otherTyped.accountNumber &&
expiryTime == otherTyped.expiryTime &&
routeNumber == otherTyped.routeNumber;
}
@override
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode]);
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, expiryTime.hashCode, routeNumber.hashCode]);
Map<String, dynamic> toJson() {
@@ -96,6 +99,9 @@ class FilterAccountsAccounts {
if (accountNumber != null) {
json['accountNumber'] = nativeToJson<String?>(accountNumber);
}
if (expiryTime != null) {
json['expiryTime'] = expiryTime!.toJson();
}
if (routeNumber != null) {
json['routeNumber'] = nativeToJson<String?>(routeNumber);
}
@@ -110,6 +116,7 @@ class FilterAccountsAccounts {
this.isPrimary,
required this.ownerId,
this.accountNumber,
this.expiryTime,
this.routeNumber,
});
}

View File

@@ -27,6 +27,7 @@ class GetAccountByIdAccount {
final String ownerId;
final String? accountNumber;
final String? routeNumber;
final Timestamp? expiryTime;
final Timestamp? createdAt;
GetAccountByIdAccount.fromJson(dynamic json):
@@ -38,6 +39,7 @@ class GetAccountByIdAccount {
ownerId = nativeFromJson<String>(json['ownerId']),
accountNumber = json['accountNumber'] == null ? null : nativeFromJson<String>(json['accountNumber']),
routeNumber = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']),
expiryTime = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']),
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']);
@override
bool operator ==(Object other) {
@@ -57,11 +59,12 @@ class GetAccountByIdAccount {
ownerId == otherTyped.ownerId &&
accountNumber == otherTyped.accountNumber &&
routeNumber == otherTyped.routeNumber &&
expiryTime == otherTyped.expiryTime &&
createdAt == otherTyped.createdAt;
}
@override
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, createdAt.hashCode]);
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, expiryTime.hashCode, createdAt.hashCode]);
Map<String, dynamic> toJson() {
@@ -82,6 +85,9 @@ class GetAccountByIdAccount {
if (routeNumber != null) {
json['routeNumber'] = nativeToJson<String?>(routeNumber);
}
if (expiryTime != null) {
json['expiryTime'] = expiryTime!.toJson();
}
if (createdAt != null) {
json['createdAt'] = createdAt!.toJson();
}
@@ -97,6 +103,7 @@ class GetAccountByIdAccount {
required this.ownerId,
this.accountNumber,
this.routeNumber,
this.expiryTime,
this.createdAt,
});
}

View File

@@ -27,6 +27,7 @@ class GetAccountsByOwnerIdAccounts {
final String ownerId;
final String? accountNumber;
final String? routeNumber;
final Timestamp? expiryTime;
final Timestamp? createdAt;
GetAccountsByOwnerIdAccounts.fromJson(dynamic json):
@@ -38,6 +39,7 @@ class GetAccountsByOwnerIdAccounts {
ownerId = nativeFromJson<String>(json['ownerId']),
accountNumber = json['accountNumber'] == null ? null : nativeFromJson<String>(json['accountNumber']),
routeNumber = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']),
expiryTime = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']),
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']);
@override
bool operator ==(Object other) {
@@ -57,11 +59,12 @@ class GetAccountsByOwnerIdAccounts {
ownerId == otherTyped.ownerId &&
accountNumber == otherTyped.accountNumber &&
routeNumber == otherTyped.routeNumber &&
expiryTime == otherTyped.expiryTime &&
createdAt == otherTyped.createdAt;
}
@override
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, createdAt.hashCode]);
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, expiryTime.hashCode, createdAt.hashCode]);
Map<String, dynamic> toJson() {
@@ -82,6 +85,9 @@ class GetAccountsByOwnerIdAccounts {
if (routeNumber != null) {
json['routeNumber'] = nativeToJson<String?>(routeNumber);
}
if (expiryTime != null) {
json['expiryTime'] = expiryTime!.toJson();
}
if (createdAt != null) {
json['createdAt'] = createdAt!.toJson();
}
@@ -97,6 +103,7 @@ class GetAccountsByOwnerIdAccounts {
required this.ownerId,
this.accountNumber,
this.routeNumber,
this.expiryTime,
this.createdAt,
});
}

View File

@@ -26,6 +26,7 @@ class ListAccountsAccounts {
final String ownerId;
final String? accountNumber;
final String? routeNumber;
final Timestamp? expiryTime;
final Timestamp? createdAt;
ListAccountsAccounts.fromJson(dynamic json):
@@ -37,6 +38,7 @@ class ListAccountsAccounts {
ownerId = nativeFromJson<String>(json['ownerId']),
accountNumber = json['accountNumber'] == null ? null : nativeFromJson<String>(json['accountNumber']),
routeNumber = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']),
expiryTime = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']),
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']);
@override
bool operator ==(Object other) {
@@ -56,11 +58,12 @@ class ListAccountsAccounts {
ownerId == otherTyped.ownerId &&
accountNumber == otherTyped.accountNumber &&
routeNumber == otherTyped.routeNumber &&
expiryTime == otherTyped.expiryTime &&
createdAt == otherTyped.createdAt;
}
@override
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, createdAt.hashCode]);
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, ownerId.hashCode, accountNumber.hashCode, routeNumber.hashCode, expiryTime.hashCode, createdAt.hashCode]);
Map<String, dynamic> toJson() {
@@ -81,6 +84,9 @@ class ListAccountsAccounts {
if (routeNumber != null) {
json['routeNumber'] = nativeToJson<String?>(routeNumber);
}
if (expiryTime != null) {
json['expiryTime'] = expiryTime!.toJson();
}
if (createdAt != null) {
json['createdAt'] = createdAt!.toJson();
}
@@ -96,6 +102,7 @@ class ListAccountsAccounts {
required this.ownerId,
this.accountNumber,
this.routeNumber,
this.expiryTime,
this.createdAt,
});
}

View File

@@ -0,0 +1,229 @@
part of 'generated.dart';
class ListShiftRolesByBusinessAndDatesSummaryVariablesBuilder {
String businessId;
Timestamp start;
Timestamp end;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
final FirebaseDataConnect _dataConnect; ListShiftRolesByBusinessAndDatesSummaryVariablesBuilder offset(int? t) {
_offset.value = t;
return this;
}
ListShiftRolesByBusinessAndDatesSummaryVariablesBuilder limit(int? t) {
_limit.value = t;
return this;
}
ListShiftRolesByBusinessAndDatesSummaryVariablesBuilder(this._dataConnect, {required this.businessId,required this.start,required this.end,});
Deserializer<ListShiftRolesByBusinessAndDatesSummaryData> dataDeserializer = (dynamic json) => ListShiftRolesByBusinessAndDatesSummaryData.fromJson(jsonDecode(json));
Serializer<ListShiftRolesByBusinessAndDatesSummaryVariables> varsSerializer = (ListShiftRolesByBusinessAndDatesSummaryVariables vars) => jsonEncode(vars.toJson());
Future<QueryResult<ListShiftRolesByBusinessAndDatesSummaryData, ListShiftRolesByBusinessAndDatesSummaryVariables>> execute() {
return ref().execute();
}
QueryRef<ListShiftRolesByBusinessAndDatesSummaryData, ListShiftRolesByBusinessAndDatesSummaryVariables> ref() {
ListShiftRolesByBusinessAndDatesSummaryVariables vars= ListShiftRolesByBusinessAndDatesSummaryVariables(businessId: businessId,start: start,end: end,offset: _offset,limit: _limit,);
return _dataConnect.query("listShiftRolesByBusinessAndDatesSummary", dataDeserializer, varsSerializer, vars);
}
}
@immutable
class ListShiftRolesByBusinessAndDatesSummaryShiftRoles {
final String roleId;
final double? hours;
final double? totalValue;
final ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole role;
ListShiftRolesByBusinessAndDatesSummaryShiftRoles.fromJson(dynamic json):
roleId = nativeFromJson<String>(json['roleId']),
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
totalValue = json['totalValue'] == null ? null : nativeFromJson<double>(json['totalValue']),
role = ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole.fromJson(json['role']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessAndDatesSummaryShiftRoles otherTyped = other as ListShiftRolesByBusinessAndDatesSummaryShiftRoles;
return roleId == otherTyped.roleId &&
hours == otherTyped.hours &&
totalValue == otherTyped.totalValue &&
role == otherTyped.role;
}
@override
int get hashCode => Object.hashAll([roleId.hashCode, hours.hashCode, totalValue.hashCode, role.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['roleId'] = nativeToJson<String>(roleId);
if (hours != null) {
json['hours'] = nativeToJson<double?>(hours);
}
if (totalValue != null) {
json['totalValue'] = nativeToJson<double?>(totalValue);
}
json['role'] = role.toJson();
return json;
}
ListShiftRolesByBusinessAndDatesSummaryShiftRoles({
required this.roleId,
this.hours,
this.totalValue,
required this.role,
});
}
@immutable
class ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole {
final String id;
final String name;
ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
name = nativeFromJson<String>(json['name']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole otherTyped = other as ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole;
return id == otherTyped.id &&
name == otherTyped.name;
}
@override
int get hashCode => Object.hashAll([id.hashCode, name.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
json['name'] = nativeToJson<String>(name);
return json;
}
ListShiftRolesByBusinessAndDatesSummaryShiftRolesRole({
required this.id,
required this.name,
});
}
@immutable
class ListShiftRolesByBusinessAndDatesSummaryData {
final List<ListShiftRolesByBusinessAndDatesSummaryShiftRoles> shiftRoles;
ListShiftRolesByBusinessAndDatesSummaryData.fromJson(dynamic json):
shiftRoles = (json['shiftRoles'] as List<dynamic>)
.map((e) => ListShiftRolesByBusinessAndDatesSummaryShiftRoles.fromJson(e))
.toList();
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessAndDatesSummaryData otherTyped = other as ListShiftRolesByBusinessAndDatesSummaryData;
return shiftRoles == otherTyped.shiftRoles;
}
@override
int get hashCode => shiftRoles.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['shiftRoles'] = shiftRoles.map((e) => e.toJson()).toList();
return json;
}
ListShiftRolesByBusinessAndDatesSummaryData({
required this.shiftRoles,
});
}
@immutable
class ListShiftRolesByBusinessAndDatesSummaryVariables {
final String businessId;
final Timestamp start;
final Timestamp end;
late final Optional<int>offset;
late final Optional<int>limit;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
ListShiftRolesByBusinessAndDatesSummaryVariables.fromJson(Map<String, dynamic> json):
businessId = nativeFromJson<String>(json['businessId']),
start = Timestamp.fromJson(json['start']),
end = Timestamp.fromJson(json['end']) {
offset = Optional.optional(nativeFromJson, nativeToJson);
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
}
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessAndDatesSummaryVariables otherTyped = other as ListShiftRolesByBusinessAndDatesSummaryVariables;
return businessId == otherTyped.businessId &&
start == otherTyped.start &&
end == otherTyped.end &&
offset == otherTyped.offset &&
limit == otherTyped.limit;
}
@override
int get hashCode => Object.hashAll([businessId.hashCode, start.hashCode, end.hashCode, offset.hashCode, limit.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['businessId'] = nativeToJson<String>(businessId);
json['start'] = start.toJson();
json['end'] = end.toJson();
if(offset.state == OptionalState.set) {
json['offset'] = offset.toJson();
}
if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson();
}
return json;
}
ListShiftRolesByBusinessAndDatesSummaryVariables({
required this.businessId,
required this.start,
required this.end,
required this.offset,
required this.limit,
});
}

View File

@@ -8,6 +8,7 @@ class UpdateAccountVariablesBuilder {
Optional<bool> _isPrimary = Optional.optional(nativeFromJson, nativeToJson);
Optional<String> _accountNumber = Optional.optional(nativeFromJson, nativeToJson);
Optional<String> _routeNumber = Optional.optional(nativeFromJson, nativeToJson);
Optional<Timestamp> _expiryTime = Optional.optional((json) => json['expiryTime'] = Timestamp.fromJson(json['expiryTime']), defaultSerializer);
final FirebaseDataConnect _dataConnect; UpdateAccountVariablesBuilder bank(String? t) {
_bank.value = t;
@@ -33,6 +34,10 @@ class UpdateAccountVariablesBuilder {
_routeNumber.value = t;
return this;
}
UpdateAccountVariablesBuilder expiryTime(Timestamp? t) {
_expiryTime.value = t;
return this;
}
UpdateAccountVariablesBuilder(this._dataConnect, {required this.id,});
Deserializer<UpdateAccountData> dataDeserializer = (dynamic json) => UpdateAccountData.fromJson(jsonDecode(json));
@@ -42,7 +47,7 @@ class UpdateAccountVariablesBuilder {
}
MutationRef<UpdateAccountData, UpdateAccountVariables> ref() {
UpdateAccountVariables vars= UpdateAccountVariables(id: id,bank: _bank,type: _type,last4: _last4,isPrimary: _isPrimary,accountNumber: _accountNumber,routeNumber: _routeNumber,);
UpdateAccountVariables vars= UpdateAccountVariables(id: id,bank: _bank,type: _type,last4: _last4,isPrimary: _isPrimary,accountNumber: _accountNumber,routeNumber: _routeNumber,expiryTime: _expiryTime,);
return _dataConnect.mutation("updateAccount", dataDeserializer, varsSerializer, vars);
}
}
@@ -126,6 +131,7 @@ class UpdateAccountVariables {
late final Optional<bool>isPrimary;
late final Optional<String>accountNumber;
late final Optional<String>routeNumber;
late final Optional<Timestamp>expiryTime;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
UpdateAccountVariables.fromJson(Map<String, dynamic> json):
@@ -156,6 +162,10 @@ class UpdateAccountVariables {
routeNumber = Optional.optional(nativeFromJson, nativeToJson);
routeNumber.value = json['routeNumber'] == null ? null : nativeFromJson<String>(json['routeNumber']);
expiryTime = Optional.optional((json) => json['expiryTime'] = Timestamp.fromJson(json['expiryTime']), defaultSerializer);
expiryTime.value = json['expiryTime'] == null ? null : Timestamp.fromJson(json['expiryTime']);
}
@override
bool operator ==(Object other) {
@@ -173,11 +183,12 @@ class UpdateAccountVariables {
last4 == otherTyped.last4 &&
isPrimary == otherTyped.isPrimary &&
accountNumber == otherTyped.accountNumber &&
routeNumber == otherTyped.routeNumber;
routeNumber == otherTyped.routeNumber &&
expiryTime == otherTyped.expiryTime;
}
@override
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, accountNumber.hashCode, routeNumber.hashCode]);
int get hashCode => Object.hashAll([id.hashCode, bank.hashCode, type.hashCode, last4.hashCode, isPrimary.hashCode, accountNumber.hashCode, routeNumber.hashCode, expiryTime.hashCode]);
Map<String, dynamic> toJson() {
@@ -201,6 +212,9 @@ class UpdateAccountVariables {
if(routeNumber.state == OptionalState.set) {
json['routeNumber'] = routeNumber.toJson();
}
if(expiryTime.state == OptionalState.set) {
json['expiryTime'] = expiryTime.toJson();
}
return json;
}
@@ -212,6 +226,7 @@ class UpdateAccountVariables {
required this.isPrimary,
required this.accountNumber,
required this.routeNumber,
required this.expiryTime,
});
}

View File

@@ -13,6 +13,8 @@ class FinancialRepositoryMock {
totalAmount: 1500.0,
workAmount: 1400.0,
addonsAmount: 100.0,
invoiceNumber: 'INV-1',
issueDate: null,
),
];
}

View File

@@ -35,6 +35,8 @@ class Invoice extends Equatable {
required this.totalAmount,
required this.workAmount,
required this.addonsAmount,
this.invoiceNumber,
this.issueDate,
});
/// Unique identifier.
final String id;
@@ -57,6 +59,12 @@ class Invoice extends Equatable {
/// Total amount for addons/extras.
final double addonsAmount;
/// Human-readable invoice number.
final String? invoiceNumber;
/// Date when the invoice was issued.
final DateTime? issueDate;
@override
List<Object?> get props => <Object?>[
id,
@@ -66,5 +74,7 @@ class Invoice extends Equatable {
totalAmount,
workAmount,
addonsAmount,
invoiceNumber,
issueDate,
];
}
}

View File

@@ -23,6 +23,7 @@ class BillingModule extends Module {
i.addSingleton<BillingRepository>(
() => BillingRepositoryImpl(
financialRepository: i.get<FinancialRepositoryMock>(),
dataConnect: ExampleConnector.instance,
),
);

View File

@@ -1,5 +1,7 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as data_connect;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/models/billing_period.dart';
import '../../domain/repositories/billing_repository.dart';
/// Implementation of [BillingRepository] in the Data layer.
@@ -16,9 +18,12 @@ class BillingRepositoryImpl implements BillingRepository {
/// Requires the [financialRepository] to fetch financial data.
BillingRepositoryImpl({
required data_connect.FinancialRepositoryMock financialRepository,
}) : _financialRepository = financialRepository;
required data_connect.ExampleConnector dataConnect,
}) : _financialRepository = financialRepository,
_dataConnect = dataConnect;
final data_connect.FinancialRepositoryMock _financialRepository;
final data_connect.ExampleConnector _dataConnect;
/// Fetches the current bill amount by aggregating open invoices.
@override
@@ -39,12 +44,22 @@ class BillingRepositoryImpl implements BillingRepository {
/// Fetches the history of paid invoices.
@override
Future<List<Invoice>> getInvoiceHistory() async {
final List<Invoice> invoices = await _financialRepository.getInvoices(
'current_business',
);
return invoices
.where((Invoice i) => i.status == InvoiceStatus.paid)
.toList();
final String? businessId =
data_connect.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <Invoice>[];
}
final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
data_connect.ListInvoicesByBusinessIdVariables> result =
await _dataConnect
.listInvoicesByBusinessId(
businessId: businessId,
)
.limit(10)
.execute();
return result.data.invoices.map(_mapInvoice).toList();
}
/// Fetches pending invoices (Open or Disputed).
@@ -66,15 +81,156 @@ class BillingRepositoryImpl implements BillingRepository {
@override
Future<double> getSavingsAmount() async {
// Simulating savings calculation (e.g., comparing to market rates).
await Future<void>.delayed(const Duration(milliseconds: 500));
return 320.00;
await Future<void>.delayed(const Duration(milliseconds: 0));
return 0.0;
}
/// Fetches the breakdown of spending.
@override
Future<List<InvoiceItem>> getSpendingBreakdown() async {
// Assuming breakdown is based on the current period's invoice items.
// We fetch items for a dummy invoice ID representing the current period.
return _financialRepository.getInvoiceItems('current_period_invoice');
Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async {
final String? businessId =
data_connect.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <InvoiceItem>[];
}
final DateTime now = DateTime.now();
final DateTime start;
final DateTime end;
if (period == BillingPeriod.week) {
final int daysFromMonday = now.weekday - DateTime.monday;
final DateTime monday = DateTime(
now.year,
now.month,
now.day,
).subtract(Duration(days: daysFromMonday));
start = DateTime(monday.year, monday.month, monday.day);
end = DateTime(monday.year, monday.month, monday.day + 6, 23, 59, 59, 999);
} else {
start = DateTime(now.year, now.month, 1);
end = DateTime(now.year, now.month + 1, 0, 23, 59, 59, 999);
}
final fdc.QueryResult<
data_connect.ListShiftRolesByBusinessAndDatesSummaryData,
data_connect.ListShiftRolesByBusinessAndDatesSummaryVariables> result =
await _dataConnect
.listShiftRolesByBusinessAndDatesSummary(
businessId: businessId,
start: _toTimestamp(start),
end: _toTimestamp(end),
)
.execute();
final List<data_connect.ListShiftRolesByBusinessAndDatesSummaryShiftRoles>
shiftRoles = result.data.shiftRoles;
if (shiftRoles.isEmpty) {
return <InvoiceItem>[];
}
final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
for (final data_connect.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role
in shiftRoles) {
final String roleId = role.roleId;
final String roleName = role.role.name;
final double hours = role.hours ?? 0.0;
final double totalValue = role.totalValue ?? 0.0;
final _RoleSummary? existing = summary[roleId];
if (existing == null) {
summary[roleId] = _RoleSummary(
roleId: roleId,
roleName: roleName,
totalHours: hours,
totalValue: totalValue,
);
} else {
summary[roleId] = existing.copyWith(
totalHours: existing.totalHours + hours,
totalValue: existing.totalValue + totalValue,
);
}
}
return summary.values
.map(
(_RoleSummary item) => InvoiceItem(
id: item.roleId,
invoiceId: item.roleId,
staffId: item.roleName,
workHours: item.totalHours,
rate: item.totalHours > 0 ? item.totalValue / item.totalHours : 0,
amount: item.totalValue,
),
)
.toList();
}
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
Invoice _mapInvoice(data_connect.ListInvoicesByBusinessIdInvoices invoice) {
return Invoice(
id: invoice.id,
eventId: invoice.orderId,
businessId: invoice.businessId,
status: _mapInvoiceStatus(invoice.status),
totalAmount: invoice.amount,
workAmount: invoice.amount,
addonsAmount: invoice.otherCharges ?? 0,
invoiceNumber: invoice.invoiceNumber,
issueDate: invoice.issueDate.toDateTime(),
);
}
InvoiceStatus _mapInvoiceStatus(
data_connect.EnumValue<data_connect.InvoiceStatus> status,
) {
if (status is data_connect.Known<data_connect.InvoiceStatus>) {
switch (status.value) {
case data_connect.InvoiceStatus.PAID:
return InvoiceStatus.paid;
case data_connect.InvoiceStatus.OVERDUE:
return InvoiceStatus.overdue;
case data_connect.InvoiceStatus.DISPUTED:
return InvoiceStatus.disputed;
case data_connect.InvoiceStatus.APPROVED:
return InvoiceStatus.verified;
case data_connect.InvoiceStatus.PENDING_REVIEW:
case data_connect.InvoiceStatus.PENDING:
case data_connect.InvoiceStatus.DRAFT:
return InvoiceStatus.open;
}
}
return InvoiceStatus.open;
}
}
class _RoleSummary {
const _RoleSummary({
required this.roleId,
required this.roleName,
required this.totalHours,
required this.totalValue,
});
final String roleId;
final String roleName;
final double totalHours;
final double totalValue;
_RoleSummary copyWith({
double? totalHours,
double? totalValue,
}) {
return _RoleSummary(
roleId: roleId,
roleName: roleName,
totalHours: totalHours ?? this.totalHours,
totalValue: totalValue ?? this.totalValue,
);
}
}

View File

@@ -0,0 +1,4 @@
enum BillingPeriod {
week,
month,
}

View File

@@ -1,4 +1,5 @@
import 'package:krow_domain/krow_domain.dart';
import '../models/billing_period.dart';
/// Repository interface for billing related operations.
///
@@ -19,5 +20,5 @@ abstract class BillingRepository {
Future<double> getSavingsAmount();
/// Fetches invoice items for spending breakdown analysis.
Future<List<InvoiceItem>> getSpendingBreakdown();
Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period);
}

View File

@@ -1,17 +1,20 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../models/billing_period.dart';
import '../repositories/billing_repository.dart';
/// Use case for fetching the spending breakdown items.
///
/// This use case encapsulates the logic for retrieving the spending breakdown by category or item.
/// It delegates the data retrieval to the [BillingRepository].
class GetSpendingBreakdownUseCase extends NoInputUseCase<List<InvoiceItem>> {
class GetSpendingBreakdownUseCase
extends UseCase<BillingPeriod, List<InvoiceItem>> {
/// Creates a [GetSpendingBreakdownUseCase].
GetSpendingBreakdownUseCase(this._repository);
final BillingRepository _repository;
@override
Future<List<InvoiceItem>> call() => _repository.getSpendingBreakdown();
Future<List<InvoiceItem>> call(BillingPeriod period) =>
_repository.getSpendingBreakdown(period);
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/models/billing_period.dart';
import '../../domain/usecases/get_current_bill_amount.dart';
import '../../domain/usecases/get_invoice_history.dart';
import '../../domain/usecases/get_pending_invoices.dart';
@@ -26,6 +27,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
_getSpendingBreakdown = getSpendingBreakdown,
super(const BillingState()) {
on<BillingLoadStarted>(_onLoadStarted);
on<BillingPeriodChanged>(_onPeriodChanged);
}
final GetCurrentBillAmountUseCase _getCurrentBillAmount;
@@ -45,7 +47,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
_getSavingsAmount.call(),
_getPendingInvoices.call(),
_getInvoiceHistory.call(),
_getSpendingBreakdown.call(),
_getSpendingBreakdown.call(state.period),
]);
final double currentBill = results[0] as double;
@@ -62,11 +64,15 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
.map(_mapInvoiceToUiModel)
.toList();
final List<SpendingBreakdownItem> uiSpendingBreakdown = _mapSpendingItemsToUiModel(spendingItems);
final double periodTotal = uiSpendingBreakdown.fold(
0.0,
(double sum, SpendingBreakdownItem item) => sum + item.amount,
);
emit(
state.copyWith(
status: BillingStatus.success,
currentBill: currentBill,
currentBill: periodTotal,
savings: savings,
pendingInvoices: uiPendingInvoices,
invoiceHistory: uiInvoiceHistory,
@@ -83,37 +89,66 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
}
}
Future<void> _onPeriodChanged(
BillingPeriodChanged event,
Emitter<BillingState> emit,
) async {
try {
final List<InvoiceItem> spendingItems =
await _getSpendingBreakdown.call(event.period);
final List<SpendingBreakdownItem> uiSpendingBreakdown =
_mapSpendingItemsToUiModel(spendingItems);
final double periodTotal = uiSpendingBreakdown.fold(
0.0,
(double sum, SpendingBreakdownItem item) => sum + item.amount,
);
emit(
state.copyWith(
period: event.period,
spendingBreakdown: uiSpendingBreakdown,
currentBill: periodTotal,
),
);
} catch (e) {
emit(
state.copyWith(
status: BillingStatus.failure,
errorMessage: e.toString(),
),
);
}
}
BillingInvoice _mapInvoiceToUiModel(Invoice invoice) {
// In a real app, fetches related Event/Business names via ID.
// For now, mapping available fields and hardcoding missing UI placeholders.
// Preserving "Existing Behavior" means we show something.
final String dateLabel = invoice.issueDate == null
? '2024-01-24'
: invoice.issueDate!.toIso8601String().split('T').first;
final String titleLabel = invoice.invoiceNumber ?? invoice.id;
return BillingInvoice(
id: invoice.id,
id: titleLabel,
title: 'Invoice #${invoice.id}', // Placeholder as Invoice lacks title
locationAddress:
'Location for ${invoice.eventId}', // Placeholder for address
clientName: 'Client ${invoice.businessId}', // Placeholder for client name
date: '2024-01-24', // Placeholder date
date: dateLabel,
totalAmount: invoice.totalAmount,
workersCount: 5, // Placeholder count
totalHours: invoice.workAmount / 25.0, // Estimating hours from amount
status: invoice.status.name,
status: invoice.status.name.toUpperCase(),
);
}
List<SpendingBreakdownItem> _mapSpendingItemsToUiModel(
List<InvoiceItem> items,
) {
// Aggregating items by some logic.
// Since InvoiceItem doesn't have category, we mock it based on staffId or similar.
final Map<String, SpendingBreakdownItem> aggregation = <String, SpendingBreakdownItem>{};
final Map<String, SpendingBreakdownItem> aggregation =
<String, SpendingBreakdownItem>{};
for (final InvoiceItem item in items) {
// Mocking category derivation
final String category = item.staffId.hashCode % 2 == 0
? 'Server Staff'
: 'Bar Staff';
final String category = item.staffId;
final SpendingBreakdownItem? existing = aggregation[category];
if (existing != null) {
aggregation[category] = SpendingBreakdownItem(

View File

@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../domain/models/billing_period.dart';
/// Base class for all billing events.
abstract class BillingEvent extends Equatable {
@@ -14,3 +15,12 @@ class BillingLoadStarted extends BillingEvent {
/// Creates a [BillingLoadStarted] event.
const BillingLoadStarted();
}
class BillingPeriodChanged extends BillingEvent {
const BillingPeriodChanged(this.period);
final BillingPeriod period;
@override
List<Object?> get props => <Object?>[period];
}

View File

@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../domain/models/billing_period.dart';
import '../models/billing_invoice_model.dart';
import '../models/spending_breakdown_model.dart';
@@ -27,6 +28,7 @@ class BillingState extends Equatable {
this.pendingInvoices = const <BillingInvoice>[],
this.invoiceHistory = const <BillingInvoice>[],
this.spendingBreakdown = const <SpendingBreakdownItem>[],
this.period = BillingPeriod.week,
this.errorMessage,
});
@@ -48,6 +50,9 @@ class BillingState extends Equatable {
/// Breakdown of spending by category.
final List<SpendingBreakdownItem> spendingBreakdown;
/// Selected period for the breakdown.
final BillingPeriod period;
/// Error message if loading failed.
final String? errorMessage;
@@ -59,6 +64,7 @@ class BillingState extends Equatable {
List<BillingInvoice>? pendingInvoices,
List<BillingInvoice>? invoiceHistory,
List<SpendingBreakdownItem>? spendingBreakdown,
BillingPeriod? period,
String? errorMessage,
}) {
return BillingState(
@@ -68,6 +74,7 @@ class BillingState extends Equatable {
pendingInvoices: pendingInvoices ?? this.pendingInvoices,
invoiceHistory: invoiceHistory ?? this.invoiceHistory,
spendingBreakdown: spendingBreakdown ?? this.spendingBreakdown,
period: period ?? this.period,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@@ -80,6 +87,7 @@ class BillingState extends Equatable {
pendingInvoices,
invoiceHistory,
spendingBreakdown,
period,
errorMessage,
];
}

View File

@@ -114,7 +114,7 @@ class _InvoiceItem extends StatelessWidget {
'\$${invoice.totalAmount.toStringAsFixed(2)}',
style: UiTypography.body2b.textPrimary,
),
const _PaidBadge(),
_StatusBadge(status: invoice.status),
],
),
const SizedBox(width: UiConstants.space2),
@@ -125,21 +125,24 @@ class _InvoiceItem extends StatelessWidget {
}
}
class _PaidBadge extends StatelessWidget {
const _PaidBadge();
class _StatusBadge extends StatelessWidget {
const _StatusBadge({required this.status});
final String status;
@override
Widget build(BuildContext context) {
final bool isPaid = status.toUpperCase() == 'PAID';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: UiColors.tagSuccess,
color: isPaid ? UiColors.tagSuccess : UiColors.tagPending,
borderRadius: BorderRadius.circular(4),
),
child: Text(
t.client_billing.paid_badge,
isPaid ? t.client_billing.paid_badge : t.client_billing.pending_badge,
style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.iconSuccess,
color: isPaid ? UiColors.iconSuccess : UiColors.textWarning,
),
),
);

View File

@@ -1,111 +1,176 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
/// Card showing the current payment method.
class PaymentMethodCard extends StatelessWidget {
class PaymentMethodCard extends StatefulWidget {
/// Creates a [PaymentMethodCard].
const PaymentMethodCard({super.key});
@override
State<PaymentMethodCard> createState() => _PaymentMethodCardState();
}
class _PaymentMethodCardState extends State<PaymentMethodCard> {
late final Future<dc.GetAccountsByOwnerIdData?> _accountsFuture =
_loadAccounts();
Future<dc.GetAccountsByOwnerIdData?> _loadAccounts() async {
final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return null;
}
final fdc.QueryResult<dc.GetAccountsByOwnerIdData,
dc.GetAccountsByOwnerIdVariables> result =
await dc.ExampleConnector.instance
.getAccountsByOwnerId(ownerId: businessId)
.execute();
return result.data;
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.04),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
t.client_billing.payment_method,
style: UiTypography.title2b.textPrimary,
),
GestureDetector(
onTap: () {},
child: Row(
children: <Widget>[
const Icon(UiIcons.add, size: 14, color: UiColors.primary),
const SizedBox(width: 4),
Text(
t.client_billing.add_payment,
style: UiTypography.footnote2b.textPrimary,
),
],
),
return FutureBuilder<dc.GetAccountsByOwnerIdData?>(
future: _accountsFuture,
builder: (BuildContext context,
AsyncSnapshot<dc.GetAccountsByOwnerIdData?> snapshot) {
final List<dc.GetAccountsByOwnerIdAccounts> accounts =
snapshot.data?.accounts ??
<dc.GetAccountsByOwnerIdAccounts>[];
final dc.GetAccountsByOwnerIdAccounts? account =
accounts.isNotEmpty ? accounts.first : null;
final String bankLabel =
account?.bank.isNotEmpty == true ? account!.bank : '----';
final String last4 =
account?.last4.isNotEmpty == true ? account!.last4 : '----';
final bool isPrimary = account?.isPrimary ?? false;
final String expiryLabel = _formatExpiry(account?.expiryTime);
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.04),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
const SizedBox(height: UiConstants.space3),
Container(
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusMd,
),
child: Row(
children: <Widget>[
Container(
width: 40,
height: 28,
decoration: BoxDecoration(
color: UiColors.primary,
borderRadius: BorderRadius.circular(4),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
t.client_billing.payment_method,
style: UiTypography.title2b.textPrimary,
),
child: const Center(
child: Text(
'VISA',
style: TextStyle(
color: UiColors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
GestureDetector(
onTap: () {},
child: Row(
children: <Widget>[
const Icon(
UiIcons.add,
size: 14,
color: UiColors.primary,
),
const SizedBox(width: 4),
Text(
t.client_billing.add_payment,
style: UiTypography.footnote2b.textPrimary,
),
],
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
],
),
if (account != null) ...<Widget>[
const SizedBox(height: UiConstants.space3),
Container(
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusMd,
),
child: Row(
children: <Widget>[
Text('•••• 4242', style: UiTypography.body2b.textPrimary),
Text(
t.client_billing.expires(date: '12/25'),
style: UiTypography.footnote2r.textSecondary,
Container(
width: 40,
height: 28,
decoration: BoxDecoration(
color: UiColors.primary,
borderRadius: BorderRadius.circular(4),
),
child: Center(
child: Text(
bankLabel,
style: const TextStyle(
color: UiColors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'•••• $last4',
style: UiTypography.body2b.textPrimary,
),
Text(
t.client_billing.expires(date: expiryLabel),
style: UiTypography.footnote2r.textSecondary,
),
],
),
),
if (isPrimary)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: UiColors.accent,
borderRadius: BorderRadius.circular(4),
),
child: Text(
t.client_billing.default_badge,
style: UiTypography.titleUppercase4b.textPrimary,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: UiColors.accent,
borderRadius: BorderRadius.circular(4),
),
child: Text(
t.client_billing.default_badge,
style: UiTypography.titleUppercase4b.textPrimary,
),
),
],
),
],
),
],
),
);
},
);
}
String _formatExpiry(fdc.Timestamp? expiryTime) {
if (expiryTime == null) {
return 'N/A';
}
final DateTime date = expiryTime.toDateTime();
final String month = date.month.toString().padLeft(2, '0');
final String year = (date.year % 100).toString().padLeft(2, '0');
return '$month/$year';
}
}

View File

@@ -2,8 +2,10 @@ 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 '../../domain/models/billing_period.dart';
import '../blocs/billing_bloc.dart';
import '../blocs/billing_state.dart';
import '../blocs/billing_event.dart';
import '../models/spending_breakdown_model.dart';
/// Card showing the spending breakdown for the current period.
@@ -92,6 +94,13 @@ class _SpendingBreakdownCardState extends State<SpendingBreakdownCard>
indicatorSize: TabBarIndicatorSize.tab,
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
dividerColor: Colors.transparent,
onTap: (int index) {
final BillingPeriod period =
index == 0 ? BillingPeriod.week : BillingPeriod.month;
context.read<BillingBloc>().add(
BillingPeriodChanged(period),
);
},
tabs: <Widget>[
Tab(text: t.client_billing.week),
Tab(text: t.client_billing.month),