feat: enhance job detail screen to display project branch information and improve payment request handling
This commit is contained in:
parent
d0b40a9822
commit
87a7d19672
@ -13,6 +13,7 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:marco/controller/finance/payment_request_controller.dart';
|
||||||
|
|
||||||
class PaymentRequestDetailController extends GetxController {
|
class PaymentRequestDetailController extends GetxController {
|
||||||
final Rx<PaymentRequestData?> paymentRequest = Rx<PaymentRequestData?>(null);
|
final Rx<PaymentRequestData?> paymentRequest = Rx<PaymentRequestData?>(null);
|
||||||
@ -26,6 +27,8 @@ class PaymentRequestDetailController extends GetxController {
|
|||||||
final RxList<EmployeeModel> employeeSearchResults = <EmployeeModel>[].obs;
|
final RxList<EmployeeModel> employeeSearchResults = <EmployeeModel>[].obs;
|
||||||
final TextEditingController employeeSearchController =
|
final TextEditingController employeeSearchController =
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
|
PaymentRequestController get paymentRequestController =>
|
||||||
|
Get.find<PaymentRequestController>();
|
||||||
final RxBool isSearchingEmployees = false.obs;
|
final RxBool isSearchingEmployees = false.obs;
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
@ -297,6 +300,7 @@ class PaymentRequestDetailController extends GetxController {
|
|||||||
message: 'Payment submitted successfully',
|
message: 'Payment submitted successfully',
|
||||||
type: SnackbarType.success);
|
type: SnackbarType.success);
|
||||||
await fetchPaymentRequestDetail();
|
await fetchPaymentRequestDetail();
|
||||||
|
paymentRequestController.fetchPaymentRequests();
|
||||||
} else {
|
} else {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
|
|||||||
@ -305,7 +305,6 @@ class ExpenseList extends StatelessWidget {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await Get.to(
|
final result = await Get.to(
|
||||||
() => ExpenseDetailScreen(expenseId: expense.id),
|
() => ExpenseDetailScreen(expenseId: expense.id),
|
||||||
arguments: {'expense': expense},
|
|
||||||
);
|
);
|
||||||
if (result == true && onViewDetail != null) {
|
if (result == true && onViewDetail != null) {
|
||||||
await onViewDetail!();
|
await onViewDetail!();
|
||||||
|
|||||||
@ -47,6 +47,7 @@ class JobData {
|
|||||||
final User? createdBy;
|
final User? createdBy;
|
||||||
final List<Tag>? tags;
|
final List<Tag>? tags;
|
||||||
final List<UpdateLog>? updateLogs;
|
final List<UpdateLog>? updateLogs;
|
||||||
|
final ProjectBranch? projectBranch;
|
||||||
|
|
||||||
JobData({
|
JobData({
|
||||||
this.id,
|
this.id,
|
||||||
@ -66,6 +67,7 @@ class JobData {
|
|||||||
this.createdBy,
|
this.createdBy,
|
||||||
this.tags,
|
this.tags,
|
||||||
this.updateLogs,
|
this.updateLogs,
|
||||||
|
this.projectBranch,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory JobData.fromJson(Map<String, dynamic>? json) {
|
factory JobData.fromJson(Map<String, dynamic>? json) {
|
||||||
@ -76,7 +78,8 @@ class JobData {
|
|||||||
title: json['title'] as String?,
|
title: json['title'] as String?,
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
jobTicketUId: json['jobTicketUId'] as String?,
|
jobTicketUId: json['jobTicketUId'] as String?,
|
||||||
project: json['project'] != null ? Project.fromJson(json['project']) : null,
|
project:
|
||||||
|
json['project'] != null ? Project.fromJson(json['project']) : null,
|
||||||
assignees: (json['assignees'] as List<dynamic>?)
|
assignees: (json['assignees'] as List<dynamic>?)
|
||||||
?.map((e) => Assignee.fromJson(e))
|
?.map((e) => Assignee.fromJson(e))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@ -89,7 +92,8 @@ class JobData {
|
|||||||
attendanceId: json['attendanceId'] as String?,
|
attendanceId: json['attendanceId'] as String?,
|
||||||
nextTaggingAction: json['nextTaggingAction'] as int?,
|
nextTaggingAction: json['nextTaggingAction'] as int?,
|
||||||
createdAt: json['createdAt'] as String?,
|
createdAt: json['createdAt'] as String?,
|
||||||
createdBy: json['createdBy'] != null ? User.fromJson(json['createdBy']) : null,
|
createdBy:
|
||||||
|
json['createdBy'] != null ? User.fromJson(json['createdBy']) : null,
|
||||||
tags: (json['tags'] as List<dynamic>?)
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
?.map((e) => Tag.fromJson(e))
|
?.map((e) => Tag.fromJson(e))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@ -98,6 +102,9 @@ class JobData {
|
|||||||
?.map((e) => UpdateLog.fromJson(e))
|
?.map((e) => UpdateLog.fromJson(e))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
|
projectBranch: json['projectBranch'] != null
|
||||||
|
? ProjectBranch.fromJson(json['projectBranch'])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,6 +177,26 @@ class Assignee {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ProjectBranch {
|
||||||
|
final String? id;
|
||||||
|
final String? branchName;
|
||||||
|
final String? branchType;
|
||||||
|
|
||||||
|
ProjectBranch({
|
||||||
|
this.id,
|
||||||
|
this.branchName,
|
||||||
|
this.branchType,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ProjectBranch.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ProjectBranch(
|
||||||
|
id: json['id'],
|
||||||
|
branchName: json['branchName'],
|
||||||
|
branchType: json['branchType'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Status {
|
class Status {
|
||||||
final String? id;
|
final String? id;
|
||||||
final String? name;
|
final String? name;
|
||||||
@ -266,9 +293,12 @@ class UpdateLog {
|
|||||||
return UpdateLog(
|
return UpdateLog(
|
||||||
id: json['id'] as String?,
|
id: json['id'] as String?,
|
||||||
status: json['status'] != null ? Status.fromJson(json['status']) : null,
|
status: json['status'] != null ? Status.fromJson(json['status']) : null,
|
||||||
nextStatus: json['nextStatus'] != null ? Status.fromJson(json['nextStatus']) : null,
|
nextStatus: json['nextStatus'] != null
|
||||||
|
? Status.fromJson(json['nextStatus'])
|
||||||
|
: null,
|
||||||
comment: json['comment'] as String?,
|
comment: json['comment'] as String?,
|
||||||
updatedBy: json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null,
|
updatedBy:
|
||||||
|
json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import 'package:marco/view/finance/payment_request_detail_screen.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
|
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
|
||||||
import 'package:marco/controller/permission_controller.dart';
|
import 'package:marco/controller/permission_controller.dart';
|
||||||
import 'package:marco/helpers/utils/permission_constants.dart';
|
import 'package:marco/helpers/utils/permission_constants.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentRequestMainScreen extends StatefulWidget {
|
class PaymentRequestMainScreen extends StatefulWidget {
|
||||||
const PaymentRequestMainScreen({super.key});
|
const PaymentRequestMainScreen({super.key});
|
||||||
@ -283,10 +285,8 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
|||||||
|
|
||||||
final list = filteredList(isHistory: isHistory);
|
final list = filteredList(isHistory: isHistory);
|
||||||
|
|
||||||
// Single ScrollController for this list
|
// ScrollController for infinite scroll
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
// Load more when reaching near bottom
|
|
||||||
scrollController.addListener(() {
|
scrollController.addListener(() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 100 &&
|
scrollController.position.maxScrollExtent - 100 &&
|
||||||
@ -295,7 +295,7 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return RefreshIndicator(
|
return MyRefreshIndicator(
|
||||||
onRefresh: _refreshPaymentRequests,
|
onRefresh: _refreshPaymentRequests,
|
||||||
child: list.isEmpty
|
child: list.isEmpty
|
||||||
? ListView(
|
? ListView(
|
||||||
@ -315,14 +315,13 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
controller: scrollController, // attach controller
|
controller: scrollController,
|
||||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
|
||||||
itemCount: list.length + 1, // extra item for loading
|
itemCount: list.length + 1,
|
||||||
separatorBuilder: (_, __) =>
|
separatorBuilder: (_, __) =>
|
||||||
Divider(color: Colors.grey.shade300, height: 20),
|
Divider(color: Colors.grey.shade300, height: 20),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == list.length) {
|
if (index == list.length) {
|
||||||
// Show loading indicator at bottom
|
|
||||||
return Obx(() => paymentController.isLoading.value
|
return Obx(() => paymentController.isLoading.value
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
@ -330,7 +329,6 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
|||||||
)
|
)
|
||||||
: const SizedBox.shrink());
|
: const SizedBox.shrink());
|
||||||
}
|
}
|
||||||
|
|
||||||
final item = list[index];
|
final item = list[index];
|
||||||
return _buildPaymentRequestTile(item);
|
return _buildPaymentRequestTile(item);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -716,6 +716,40 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _rowItem(String label, String value) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: MyText.bodySmall(label,
|
||||||
|
fontWeight: 600, color: Colors.grey.shade700),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: MyText.bodyMedium(value, fontWeight: 500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _branchDisplay() {
|
||||||
|
final job = controller.jobDetail.value?.data;
|
||||||
|
final branch = job?.projectBranch;
|
||||||
|
if (branch == null) {
|
||||||
|
return MyText.labelMedium("No branch assigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_rowItem("Branch Name", branch.branchName ?? "-"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
_rowItem("Branch Type", branch.branchType ?? "-"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -765,6 +799,12 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
|
_buildSectionCard(
|
||||||
|
title: "Project Branch",
|
||||||
|
titleIcon: Icons.account_tree_outlined,
|
||||||
|
children: [_branchDisplay()],
|
||||||
|
),
|
||||||
|
MySpacing.height(16),
|
||||||
_buildSectionCard(
|
_buildSectionCard(
|
||||||
title: "Assignees",
|
title: "Assignees",
|
||||||
titleIcon: Icons.person_outline,
|
titleIcon: Icons.person_outline,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user