Browse Source

越南的工作考勤的审核

liukai 2 months ago
parent
commit
4c12ee6392

+ 1 - 1
packages/cpt_auth/lib/modules/main/main_controller.dart

@@ -148,7 +148,7 @@ class MainController extends GetxController {
         ComponentRouterServices.labourService.startLabourReviewPage();
         break;
       case 'attReview':
-        ToastEngine.show("进入 Attendance Review 模块");
+        ComponentRouterServices.jobService.startAttendanceReviewPage();
         break;
       case 'template':
         //越南的用工请求

+ 247 - 0
packages/cpt_job/lib/modules/attendance_review_list/attendance_review_controller.dart

@@ -0,0 +1,247 @@
+import 'package:domain/entity/response/attendance_review_entity.dart';
+import 'package:domain/repository/job_repository.dart';
+import 'package:get/get.dart';
+import 'package:plugin_platform/engine/dialog/dialog_engine.dart';
+import 'package:plugin_platform/engine/notify/notify_engine.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:plugin_platform/http/dio/dio_cancelable_mixin.dart';
+
+import 'package:shared/utils/log_utils.dart';
+import 'package:widgets/dialog/app_default_dialog.dart';
+import 'package:widgets/load_state_layout.dart';
+import 'package:widgets/widget_export.dart';
+
+import 'attendance_review_reject_dialog.dart';
+import 'attendance_review_state.dart';
+
+class AttendanceReviewController extends GetxController with DioCancelableMixin {
+  final JobRepository _jobRepository = Get.find();
+  final AttendanceReviewState state = AttendanceReviewState();
+
+  var _curPage = 1;
+  var _needShowPlaceholder = true;
+
+  //页面PlaceHolder的展示
+  LoadState loadingState = LoadState.State_Success;
+  String? errorMessage;
+
+  //刷新页面状态
+  void changeLoadingState(LoadState state) {
+    loadingState = state;
+    update();
+  }
+
+  // Refresh 控制器
+  final EasyRefreshController refreshController = EasyRefreshController(
+    controlFinishRefresh: true,
+    controlFinishLoad: true,
+  );
+
+  // Refresh 刷新事件
+  Future onRefresh() async {
+    _curPage = 1;
+    fetchAppliedStaffList();
+  }
+
+  // Refresh 加载事件
+  Future loadMore() async {
+    _curPage++;
+    fetchAppliedStaffList();
+  }
+
+  // 重试请求
+  Future retryRequest() async {
+    _curPage = 1;
+    _needShowPlaceholder = true;
+    fetchAppliedStaffList();
+  }
+
+  /// 获取服务器数据,通知消息列表
+  Future fetchAppliedStaffList() async {
+    if (_needShowPlaceholder) {
+      changeLoadingState(LoadState.State_Loading);
+    }
+
+    // 并发执行两个请求
+    final listResult = await _jobRepository.fetchAttendanceReviewList(
+      state.keyword,
+      curPage: _curPage,
+      cancelToken: cancelToken,
+    );
+
+    // 处理数据
+    if (listResult.isSuccess) {
+      handleList(listResult.data?.rows);
+    } else {
+      errorMessage = listResult.errorMsg;
+      changeLoadingState(LoadState.State_Error);
+    }
+
+    // 最后赋值
+    _needShowPlaceholder = false;
+  }
+
+  // 处理数据与展示的逻辑
+  void handleList(List<AttendanceReviewRows>? list) {
+    if (list != null && list.isNotEmpty) {
+      //有数据,判断是刷新还是加载更多的数据
+      if (_curPage == 1) {
+        //刷新的方式
+        state.datas.clear();
+        state.datas.addAll(list);
+        refreshController.finishRefresh();
+
+        //更新展示的状态
+        changeLoadingState(LoadState.State_Success);
+      } else {
+        //加载更多
+        state.datas.addAll(list);
+        refreshController.finishLoad();
+        update();
+      }
+    } else {
+      if (_curPage == 1) {
+        //展示无数据的布局
+        state.datas.clear();
+        changeLoadingState(LoadState.State_Empty);
+        refreshController.finishRefresh();
+      } else {
+        //展示加载完成,没有更多数据了
+        refreshController.finishLoad(IndicatorResult.noMore);
+      }
+    }
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+    fetchAppliedStaffList();
+  }
+
+  @override
+  void onClose() {
+    state.datas.clear();
+    super.onClose();
+  }
+
+  /// 搜索
+  void doSearch(String keyword) {
+    state.keyword = keyword;
+    //赋值之后刷新
+    refreshController.callRefresh();
+  }
+
+  /// 清空筛选条件
+  void resetFiltering() {
+    state.keyword = "";
+    state.searchController.text = "";
+
+    //赋值之后刷新
+    refreshController.callRefresh();
+  }
+
+  /// Item选中与未选中设置
+  void doSelectedOrNot(AttendanceReviewRows data) {
+    data.isSelected = !data.isSelected;
+    Log.d("isSelected:${data.isSelected}");
+    update();
+  }
+
+  /// 执行批量同意
+  void _requestBatchApprove(String recordIds) async {
+    //执行请求
+    var result = await _jobRepository.approveAttendanceReviews(
+      recordIds,
+      cancelToken: cancelToken,
+    );
+
+    if (result.isSuccess) {
+      NotifyEngine.showSuccess("Successful".tr);
+
+      //调用接口刷新指定的Staff的信息
+      _removeItemsByList(recordIds);
+    } else {
+      ToastEngine.show(result.errorMsg ?? "Network Load Error".tr);
+      return;
+    }
+  }
+
+  /// 执行批量拒绝
+  void _requestBatchReject(String recordIds, String? reason) async {
+    //执行请求
+    var result = await _jobRepository.rejectLabourReviews(
+      recordIds,
+      reason,
+      cancelToken: cancelToken,
+    );
+
+    if (result.isSuccess) {
+      NotifyEngine.showSuccess("Successful".tr);
+
+      //调用接口刷新指定的Staff的信息
+      _removeItemsByList(recordIds);
+    } else {
+      ToastEngine.show(result.errorMsg ?? "Network Load Error".tr);
+      return;
+    }
+  }
+
+  /// 批准的操作
+  void operationApprove() async {
+    //找出已经选中的员工(只有状态为3 Approve的状态才能修改)
+    var selectedList = state.datas.where((element) => element.isSelected).toList(growable: false);
+    if (selectedList.isNotEmpty) {
+      var ids = selectedList.map((e) => e.recordId.toString()).toList(growable: false);
+      var recordIds = ids.join(',');
+
+      // Are you sure 的弹窗
+      DialogEngine.show(
+        widget: AppDefaultDialog(
+          title: "Message".tr,
+          message: "Are you sure you want to setting approved?".tr,
+          confirmAction: () {
+            _requestBatchApprove(recordIds);
+          },
+        ),
+      );
+    } else {
+      ToastEngine.show("Please select the record".tr);
+    }
+  }
+
+  /// 拒绝的操作
+  void operationReject() async {
+    //找出已经选中的员工(只有状态为3 Approve的状态才能修改)
+    var selectedList = state.datas.where((element) => element.isSelected).toList(growable: false);
+    if (selectedList.isNotEmpty) {
+      var ids = selectedList.map((e) => e.recordId.toString()).toList(growable: false);
+      var recordIds = ids.join(',');
+
+      // Are you sure 的弹窗
+      DialogEngine.show(
+        widget: AttendaceReviewRejectDialog(
+          confirmAction: (reason) {
+            //请求接口,提交评论
+            _requestBatchReject(recordIds, reason);
+          },
+        ),
+      );
+    } else {
+      ToastEngine.show("Please select the record".tr);
+    }
+  }
+
+  /// 删除对应的recordId的Item数据
+  void _removeItemsByList(String recordIds) {
+    // 将逗号分隔的字符串转换为数组
+    List<String> recordIdList = recordIds.split(',');
+
+    // 移除列表中符合条件的项
+    state.datas.removeWhere((e) => recordIdList.contains(e.recordId));
+
+    update();
+  }
+
+  /// 去用工审核流程页面
+  void gotoStatusViewPage(AttendanceReviewRows data) {}
+}

+ 329 - 0
packages/cpt_job/lib/modules/attendance_review_list/attendance_review_item.dart

@@ -0,0 +1,329 @@
+import 'package:cs_resources/constants/color_constants.dart';
+import 'package:cs_resources/generated/assets.dart';
+import 'package:domain/entity/response/attendance_review_entity.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:plugin_basic/basic_export.dart';
+import 'package:shared/utils/util.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/my_load_image.dart';
+import 'package:widgets/my_text_view.dart';
+
+/*
+ * 用工审核的列表Item
+ */
+class AttendanceReviewItem extends StatelessWidget {
+  final int index;
+  final AttendanceReviewRows item;
+  final VoidCallback? onStatusAction;
+  final VoidCallback? onItemAction;
+
+  AttendanceReviewItem({
+    required this.index,
+    required this.item,
+    this.onStatusAction,
+    this.onItemAction,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      padding: const EdgeInsets.symmetric(vertical: 23, horizontal: 21),
+      margin: const EdgeInsets.only(left: 15, right: 15, top: 5, bottom: 5),
+      decoration: BoxDecoration(
+        color: const Color(0xFF4DCFF6).withOpacity(0.2), // 设置背景颜色和不透明度
+        borderRadius: BorderRadius.circular(5), // 设置圆角
+      ),
+      child: Column(
+        mainAxisSize: MainAxisSize.max,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children:[
+          //员工姓名
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "Staff Name:".tr,
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              //姓名
+              MyTextView(
+                item.staffName ?? "-",
+                isFontRegular: true,
+                textColor: ColorConstants.white,
+                fontSize: 14,
+                marginLeft: 5,
+                marginRight: 5,
+              ).expanded(),
+
+              //是否选中
+              MyAssetImage(
+                item.isSelected ? Assets.baseServiceItemSelectedIcon : Assets.baseServiceItemUnselectedIcon,
+                width: 20.5,
+                height: 20.5,
+              ),
+            ],
+          ),
+
+          // 工作标题
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "${"Title".tr}:",
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              MyTextView(
+                item.jobTitle ?? "-",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // 部门
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "${"Outlet".tr}:",
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              MyTextView(
+                item.departmentName ?? "-",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // 工作时间
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "DateTime:".tr,
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              MyTextView(
+                item.jobTime ?? "-",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          //考勤时间
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "${"Clock Time".tr}:",
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              //时间
+              MyTextView(
+                "${item.clockIn} - ${item.clockOut}",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+
+          // + - Hours
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "+/- Hours:".tr,
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              //小时
+              MyTextView(
+                Utils.isNotEmpty(item.adjustShow) ? item.adjustShow! : "0",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // Total Hours
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "${"Total (Hrs/Rms)".tr}:",
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              //小时
+              MyTextView(
+                item.totalShow.toString(),
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // 状态
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "Status:".tr,
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              //发布状态
+              MyTextView(
+                item.statusShow ?? "-",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: "Completed" == item.statusShow
+                    ? ColorConstants.textGreen05DC82
+                    : "Cancelled" == item.statusShow || "Rejected" == item.statusShow
+                    ? ColorConstants.textRedFF6262
+                    : "Revised" == item.statusShow || "Pending" == item.statusShow || "Approve" == item.statusShow
+                    ? ColorConstants.textYellowFFBB1B
+                    : ColorConstants.textBlue06D9FF,  //默认蓝色
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // 创建时间
+          Row(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              MyTextView(
+                "Created At:".tr,
+                isFontRegular: true,
+                textColor: ColorConstants.textGrayAECAE5,
+                fontSize: 14,
+              ),
+
+              MyTextView(
+                item.createdAt ?? "-",
+                marginLeft: 5,
+                isFontRegular: true,
+                textColor: Colors.white,
+                fontSize: 14,
+              ).expanded(),
+            ],
+          ).marginOnly(top: 12),
+
+          // //按钮组
+          // Visibility(
+          //   visible: item.actionList?.isNotEmpty ?? false,
+          //   child: Row(
+          //     mainAxisSize: MainAxisSize.max,
+          //     mainAxisAlignment: MainAxisAlignment.end,
+          //     crossAxisAlignment: CrossAxisAlignment.center,
+          //     children: [
+          //       //编辑按钮
+          //       Visibility(
+          //         visible: item.actionList?.contains("edit") ?? false,
+          //         child: MyButton(
+          //           onPressed: () {
+          //             FocusScope.of(context).unfocus();
+          //             onEditAction?.call();
+          //           },
+          //           text: "Edit".tr,
+          //           textColor: ColorConstants.white,
+          //           backgroundColor: hexToColor(
+          //             "#FFBB1B",
+          //           ),
+          //           radius: 17.25,
+          //           minWidth: 60,
+          //           minHeight: 35,
+          //         ).marginOnly(left: 12),
+          //       ),
+          //
+          //       //状态工作流按钮
+          //       Visibility(
+          //         visible: item.actionList?.contains("status") ?? false,
+          //         child: MyButton(
+          //           onPressed: () {
+          //             FocusScope.of(context).unfocus();
+          //             onStatusAction?.call();
+          //           },
+          //           text: "Status".tr,
+          //           textColor: ColorConstants.white,
+          //           backgroundColor: hexToColor("#0AC074"),
+          //           radius: 17.25,
+          //           minWidth: 60,
+          //           minHeight: 35,
+          //         ).marginOnly(left: 12),
+          //       ),
+          //
+          //       //Remark按钮
+          //       Visibility(
+          //         visible: item.actionList?.contains("remarks") ?? false,
+          //         child: MyButton(
+          //           onPressed: () {
+          //             FocusScope.of(context).unfocus();
+          //             onRemarkAction?.call();
+          //           },
+          //           text: "Remarks".tr,
+          //           textColor: ColorConstants.white,
+          //           backgroundColor: hexToColor("#56AAFF"),
+          //           radius: 17.25,
+          //           minWidth: 60,
+          //           minHeight: 35,
+          //         ).marginOnly(left: 12),
+          //       ),
+          //     ],
+          //   ).marginOnly(top: 15),
+          // ),
+        ],
+      ),
+    ).onTap(() {
+      onItemAction?.call();
+    });
+  }
+}

+ 173 - 0
packages/cpt_job/lib/modules/attendance_review_list/attendance_review_page.dart

@@ -0,0 +1,173 @@
+import 'package:cs_resources/constants/color_constants.dart';
+import 'package:cs_resources/generated/assets.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/load_state_layout.dart';
+import 'package:widgets/my_button.dart';
+import 'package:widgets/my_text_view.dart';
+import 'package:widgets/widget_export.dart';
+
+import 'attendance_review_item.dart';
+import 'attendance_review_controller.dart';
+
+import 'package:plugin_basic/base/base_state.dart';
+import 'package:plugin_basic/base/base_stateful_page.dart';
+import 'package:plugin_basic/utils/ext_get_nav.dart';
+import 'package:router/path/router_path.dart';
+import 'package:shared/utils/screen_util.dart';
+import 'package:widgets/my_appbar.dart';
+import 'attendance_review_state.dart';
+
+/*
+ * 工作考勤的审核列表
+ */
+class AttendanceReviewPage extends BaseStatefulPage<AttendanceReviewController> {
+  AttendanceReviewPage({Key? key}) : super(key: key);
+
+  //启动当前页面
+  static void startInstance() {
+    return Get.start(RouterPath.jobAttendanceReviewList);
+  }
+
+  @override
+  AttendanceReviewController createRawController() {
+    return AttendanceReviewController();
+  }
+
+  @override
+  State<AttendanceReviewPage> createState() => _LabourReviewState();
+}
+
+class _LabourReviewState extends BaseState<AttendanceReviewPage, AttendanceReviewController> {
+  late AttendanceReviewState state;
+
+  @override
+  void initState() {
+    super.initState();
+    state = controller.state;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return autoCtlGetBuilder(builder: (controller) {
+      return SafeArea(
+        bottom: true,
+        top: false,
+        child: Container(
+          width: double.infinity,
+          height: double.infinity,
+          padding: EdgeInsets.only(top: ScreenUtil.getStatusBarH(context)),
+          decoration: const BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                Color(0xFF091D44),
+                Color(0xFF245A8A),
+                Color(0xFF7F7CEC),
+              ],
+              begin: Alignment.topCenter,
+              end: Alignment.bottomCenter,
+            ),
+          ),
+          child: Column(
+            children: [
+              MyAppBar.searchTitleBar(
+                context,
+                value: state.keyword,
+                hintText: 'Title'.tr,
+                controller: state.searchController,
+                onSearch: (keyword) {
+                  controller.doSearch(keyword);
+                },
+                actions: [
+                  //重置按钮
+                  MyButton(
+                    onPressed: () {
+                      FocusScope.of(context).unfocus();
+                      controller.resetFiltering();
+                    },
+                    text: "Reset".tr,
+                    textColor: ColorConstants.white,
+                    backgroundColor: hexToColor("#2BA9F9", opacity: 0.5),
+                    radius: 17.25,
+                    minWidth: 60,
+                    minHeight: 35,
+                  ).marginOnly(right: 15),
+
+                ],
+              ),
+
+              //底部的列表
+              EasyRefresh(
+                controller: controller.refreshController,
+                onRefresh: controller.onRefresh,
+                onLoad: controller.loadMore,
+                child: LoadStateLayout(
+                  state: controller.loadingState,
+                  errorMessage: controller.errorMessage,
+                  errorRetry: () {
+                    controller.retryRequest();
+                  },
+                  successSliverWidget: [
+                    SliverList(
+                        delegate: SliverChildBuilderDelegate(
+                      (context, index) {
+                        return AttendanceReviewItem(
+                          index: index,
+                          item: state.datas[index],
+                          onStatusAction: () {
+
+                          },
+                          onItemAction: () {
+                            controller.doSelectedOrNot(state.datas[index]);
+                          },
+                        );
+                      },
+                      childCount: state.datas.length,
+                    ))
+                  ],
+                ),
+              ).expanded(),
+
+              Row(
+                mainAxisSize: MainAxisSize.max,
+                children: [
+                  //批量Approve
+                  MyTextView(
+                    "Batch Confirm".tr,
+                    fontSize: 17,
+                    isFontMedium: true,
+                    boxHeight: 48,
+                    onClick: () {
+                      controller.operationApprove();
+                    },
+                    alignment: Alignment.center,
+                    textAlign: TextAlign.center,
+                    textColor: Colors.white,
+                    backgroundColor: ColorConstants.textGreen0AC074,
+                  ).expanded(),
+
+                  //批量修改时间
+                  MyTextView(
+                    "Batch Reject".tr,
+                    fontSize: 17,
+                    isFontMedium: true,
+                    boxHeight: 48,
+                    onClick: () {
+                      controller.operationReject();
+                    },
+                    alignment: Alignment.center,
+                    textAlign: TextAlign.center,
+                    textColor: Colors.white,
+                    backgroundColor: ColorConstants.textRedFF6262,
+                  ).expanded(),
+                ],
+              ),
+            ],
+          ),
+        ),
+      );
+    });
+  }
+}

+ 187 - 0
packages/cpt_job/lib/modules/attendance_review_list/attendance_review_reject_dialog.dart

@@ -0,0 +1,187 @@
+import 'dart:ui';
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:shared/utils/util.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:cs_resources/constants/color_constants.dart';
+import 'package:widgets/my_text_view.dart';
+import 'package:widgets/widget_export.dart';
+
+/*
+ * 拒绝的弹窗
+ */
+class AttendaceReviewRejectDialog extends StatefulWidget {
+
+  void Function(String reason)? confirmAction;
+
+  AttendaceReviewRejectDialog({this.confirmAction});
+
+  @override
+  State<AttendaceReviewRejectDialog> createState() => _AttendaceReviewRejectDialogState();
+}
+
+class _AttendaceReviewRejectDialogState extends State<AttendaceReviewRejectDialog> {
+
+  late TextEditingController _controller;
+  late FocusNode _focusNode;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = TextEditingController();
+    _focusNode = FocusNode();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.center,
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: [
+        //Title (如果使用 Container 为最外层容器则默认为 match_parent 的效果,除非我们限制宽度和最大高度最小高度)
+        Container(
+          width: double.infinity,
+          decoration: const BoxDecoration(
+            color: Colors.white,
+            borderRadius: BorderRadius.all(Radius.circular(15)),
+          ),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Center(
+                child: MyTextView(
+                  "Reason for refusal".tr,
+                  fontSize: 19,
+                  isFontMedium: true,
+                  textColor: ColorConstants.black,
+                  marginTop: 23,
+                  marginLeft: 22,
+                  marginRight: 22,
+                ),
+              ),
+
+              IgnoreKeyboardDismiss(
+                child: Container(
+                  height: 130,
+                  margin: const EdgeInsets.symmetric(vertical: 19, horizontal: 22),
+                  padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 15),
+                  decoration: BoxDecoration(
+                    color: const Color(0xFFF0F0F0),
+                    border: Border.all(
+                      color: const Color(0xFFD8D8D8),
+                      width: 0.5,
+                    ),
+                  ),
+                  child: TextField(
+                    cursorColor: ColorConstants.black66,
+                    cursorWidth: 1.5,
+                    autofocus: false,
+                    enabled: true,
+                    focusNode: _focusNode,
+                    controller: _controller,
+                    // 装饰
+                    decoration: InputDecoration(
+                      isDense: true,
+                      isCollapsed: true,
+                      border: InputBorder.none,
+                      hintText: "Enter...".tr,
+                      hintStyle: const TextStyle(
+                        color: ColorConstants.black66,
+                        fontSize: 15.0,
+                        fontWeight: FontWeight.w400,
+                      ),
+                    ),
+                    style: const TextStyle(
+                      color: ColorConstants.black,
+                      fontSize: 15.0,
+                      fontWeight: FontWeight.w400,
+                    ),
+                    // 键盘动作右下角图标
+                    textInputAction: TextInputAction.done,
+                    onSubmitted: (value) {
+                      doCallbackAction();
+                    },
+                  ),
+                ),
+              ),
+
+              // 分割线
+              Container(
+                color: const Color(0XFFCECECE),
+                height: 0.5,
+              ),
+
+              //按钮组
+              Row(
+                children: [
+                  Expanded(
+                      flex: 1,
+                      child: InkWell(
+                        onTap: () {
+                          onCancel();
+                        },
+                        child: MyTextView(
+                          "Cancel".tr,
+                          fontSize: 17.5,
+                          isFontMedium: true,
+                          textAlign: TextAlign.center,
+                          textColor: const Color(0XFF0085C4),
+                          cornerRadius: 3,
+                          borderWidth: 1,
+                        ),
+                      )),
+                  Container(
+                    color: const Color(0xff09141F).withOpacity(0.13),
+                    width: 0.5,
+                  ),
+                  Expanded(
+                      flex: 1,
+                      child: InkWell(
+                        onTap: () {
+                          doCallbackAction();
+                        },
+                        child: MyTextView(
+                          "Submit".tr,
+                          marginLeft: 10,
+                          fontSize: 17.5,
+                          isFontMedium: true,
+                          textAlign: TextAlign.center,
+                          textColor: const Color(0XFF0085C4),
+                          cornerRadius: 3,
+                        ),
+                      )),
+                ],
+              ).constrained(height: 46),
+            ],
+          ),
+        ),
+      ],
+    ).constrained(width: 285);
+  }
+
+  //取消弹框
+  void onCancel() async {
+    SmartDialog.dismiss();
+  }
+
+  //执行回调
+  void doCallbackAction() {
+    _focusNode.unfocus();
+
+    final content = _controller.text.toString();
+
+    if (Utils.isEmpty(content)) {
+      ToastEngine.show("Please Enter Reason".tr);
+      return;
+    }
+
+    onCancel();
+
+    widget.confirmAction?.call(content);
+  }
+}

+ 12 - 0
packages/cpt_job/lib/modules/attendance_review_list/attendance_review_state.dart

@@ -0,0 +1,12 @@
+import 'package:domain/entity/response/attendance_review_entity.dart';
+import 'package:flutter/material.dart';
+
+class AttendanceReviewState {
+  //筛选条件
+  final TextEditingController searchController = TextEditingController();
+  String keyword = "";
+
+  //页面的列表数据
+  List<AttendanceReviewRows> datas = [];
+
+}

+ 6 - 0
packages/cpt_job/lib/router/job_service_impl.dart

@@ -3,6 +3,7 @@ import 'package:plugin_basic/basic_export.dart';
 import 'package:router/componentRouter/job_service.dart';
 import 'package:shared/utils/log_utils.dart';
 
+import '../modules/attendance_review_list/attendance_review_page.dart';
 import '../modules/sign_in_sign_out/sign_in_sign_out_page.dart';
 
 class JobServiceImpl extends GetxService implements JobService {
@@ -31,4 +32,9 @@ class JobServiceImpl extends GetxService implements JobService {
     JobListPage.startInstance();
   }
 
+  @override
+  void startAttendanceReviewPage() {
+    AttendanceReviewPage.startInstance();
+  }
+
 }

+ 7 - 0
packages/cpt_job/lib/router/page_router.dart

@@ -6,6 +6,7 @@ import 'package:cpt_job/modules/job_applied_edit/job_applied_edit_page.dart';
 import 'package:get/get.dart';
 import 'package:router/path/router_path.dart';
 
+import '../modules/attendance_review_list/attendance_review_page.dart';
 import '../modules/job_list/job_list_page.dart';
 import '../modules/job_list_detail/job_list_detail_page.dart';
 import '../modules/sign_in_sign_out/sign_in_sign_out_page.dart';
@@ -60,5 +61,11 @@ class JobPageRouter {
       page: () => AppliedStaffReviewsPage(),
     ),
 
+    //工作考勤的审核列表
+    GetPage(
+      name: RouterPath.jobAttendanceReviewList,
+      page: () => AttendanceReviewPage(),
+    ),
+
   ];
 }

+ 9 - 1
packages/cs_domain/lib/constants/api_constants.dart

@@ -10,7 +10,7 @@ class ApiConstants {
   //新加坡的域名
   static const sgBaseUrl = isServerRelease ? 'https://www.casualabour.com' : 'http://singapore-dev.casualabour.com';
 
-// =========================== 用户相关 ↓=========================================
+  // =========================== 用户相关 ↓=========================================
 
   // 酒店登录
   static const apiUserLogin = "/index.php/api/v1/hotel/login";
@@ -293,4 +293,12 @@ class ApiConstants {
   // 用工审核的批量拒绝
   static const apiLabourReviewReject = "/index.php/api/v1/hotel/lab-review/reject";
 
+  // 考勤的审核列表
+  static const apiAttendanceReviewTable = "/index.php/api/v1/hotel/att-review/table";
+
+  // 考勤的批量同意
+  static const apiAttendanceReviewApprove = "/index.php/api/v1/hotel/att-review/approve";
+
+  // 考勤的批量拒绝
+  static const apiAttendanceReviewReject = "/index.php/api/v1/hotel/att-review/reject";
 }

+ 5 - 3
packages/cs_domain/lib/entity/response/attendance_review_entity.dart

@@ -23,11 +23,11 @@ class AttendanceReviewEntity {
 @JsonSerializable()
 class AttendanceReviewRows {
 	@JSONField(name: "record_id")
-	int? recordId;
+	String? recordId;
 	@JSONField(name: "order_id")
-	int? orderId;
+	String? orderId;
 	@JSONField(name: "applied_id")
-	int? appliedId;
+	String? appliedId;
 	@JSONField(name: "staff_name")
 	String? staffName;
 	@JSONField(name: "job_title")
@@ -55,6 +55,8 @@ class AttendanceReviewRows {
 	@JSONField(name: "has_reason")
 	int? hasReason;
 
+	bool isSelected = false;
+
 	AttendanceReviewRows();
 
 	factory AttendanceReviewRows.fromJson(Map<String, dynamic> json) => $AttendanceReviewRowsFromJson(json);

+ 14 - 7
packages/cs_domain/lib/generated/json/attendance_review_entity.g.dart

@@ -35,15 +35,15 @@ extension AttendanceReviewEntityExtension on AttendanceReviewEntity {
 
 AttendanceReviewRows $AttendanceReviewRowsFromJson(Map<String, dynamic> json) {
   final AttendanceReviewRows attendanceReviewRows = AttendanceReviewRows();
-  final int? recordId = jsonConvert.convert<int>(json['record_id']);
+  final String? recordId = jsonConvert.convert<String>(json['record_id']);
   if (recordId != null) {
     attendanceReviewRows.recordId = recordId;
   }
-  final int? orderId = jsonConvert.convert<int>(json['order_id']);
+  final String? orderId = jsonConvert.convert<String>(json['order_id']);
   if (orderId != null) {
     attendanceReviewRows.orderId = orderId;
   }
-  final int? appliedId = jsonConvert.convert<int>(json['applied_id']);
+  final String? appliedId = jsonConvert.convert<String>(json['applied_id']);
   if (appliedId != null) {
     attendanceReviewRows.appliedId = appliedId;
   }
@@ -99,6 +99,10 @@ AttendanceReviewRows $AttendanceReviewRowsFromJson(Map<String, dynamic> json) {
   if (hasReason != null) {
     attendanceReviewRows.hasReason = hasReason;
   }
+  final bool? isSelected = jsonConvert.convert<bool>(json['isSelected']);
+  if (isSelected != null) {
+    attendanceReviewRows.isSelected = isSelected;
+  }
   return attendanceReviewRows;
 }
 
@@ -120,14 +124,15 @@ Map<String, dynamic> $AttendanceReviewRowsToJson(AttendanceReviewRows entity) {
   data['out_class'] = entity.outClass;
   data['created_at'] = entity.createdAt;
   data['has_reason'] = entity.hasReason;
+  data['isSelected'] = entity.isSelected;
   return data;
 }
 
 extension AttendanceReviewRowsExtension on AttendanceReviewRows {
   AttendanceReviewRows copyWith({
-    int? recordId,
-    int? orderId,
-    int? appliedId,
+    String? recordId,
+    String? orderId,
+    String? appliedId,
     String? staffName,
     String? jobTitle,
     String? departmentName,
@@ -141,6 +146,7 @@ extension AttendanceReviewRowsExtension on AttendanceReviewRows {
     int? outClass,
     String? createdAt,
     int? hasReason,
+    bool? isSelected,
   }) {
     return AttendanceReviewRows()
       ..recordId = recordId ?? this.recordId
@@ -158,6 +164,7 @@ extension AttendanceReviewRowsExtension on AttendanceReviewRows {
       ..clockOut = clockOut ?? this.clockOut
       ..outClass = outClass ?? this.outClass
       ..createdAt = createdAt ?? this.createdAt
-      ..hasReason = hasReason ?? this.hasReason;
+      ..hasReason = hasReason ?? this.hasReason
+      ..isSelected = isSelected ?? this.isSelected;
   }
 }

+ 91 - 0
packages/cs_domain/lib/repository/job_repository.dart

@@ -20,6 +20,7 @@ import 'package:shared/utils/util.dart';
 
 import '../constants/api_constants.dart';
 import '../entity/response/attendance_entity.dart';
+import '../entity/response/attendance_review_entity.dart';
 import '../entity/response/check_success_entity.dart';
 import '../entity/response/job_list_applied_work_flow_entity.dart';
 
@@ -730,4 +731,94 @@ class JobRepository extends GetxService {
     }
     return result.convert();
   }
+
+  /// 考勤审核列表
+  Future<HttpResult<AttendanceReviewEntity>> fetchAttendanceReviewList(
+      String? keyword, {
+        required int curPage,
+        CancelToken? cancelToken,
+      }) async {
+    //参数
+    Map<String, String> params = {};
+    params["cur_page"] = curPage.toString();
+    params["page_size"] = "20";
+
+    if (!Utils.isEmpty(keyword)) {
+      params["staff_name"] = keyword!;
+    }
+
+    final result = await httpProvider.requestNetResult(
+      ApiConstants.apiAttendanceReviewTable,
+      params: params,
+      cancelToken: cancelToken,
+    );
+
+    //根据返回的结果,封装原始数据为Bean/Entity对象
+    if (result.isSuccess) {
+      //重新赋值data或list
+      final json = result.getDataJson();
+      var data = AttendanceReviewEntity.fromJson(json!);
+      //重新赋值data或list
+      return result.convert<AttendanceReviewEntity>(data: data);
+    }
+    return result.convert();
+  }
+
+  /// 考勤的批量同意
+  Future<HttpResult> approveAttendanceReviews(
+      String? recordIds, {
+        CancelToken? cancelToken,
+      }) async {
+    //参数
+    Map<String, String> params = {};
+    params['record_ids'] = recordIds ?? "";
+
+    final result = await httpProvider.requestNetResult(
+      ApiConstants.apiAttendanceReviewApprove,
+      method: HttpMethod.POST,
+      params: params,
+      networkDebounce: true,
+      isShowLoadingDialog: true,
+      cancelToken: cancelToken,
+    );
+
+    //根据返回的结果,封装原始数据为Bean/Entity对象
+    if (result.isSuccess) {
+      //重新赋值data或list
+      return result.convert();
+    }
+    return result.convert();
+  }
+
+  /// 考勤的批量拒绝
+  Future<HttpResult> rejectLabourReviews(
+      String? recordIds,
+      String? reason, {
+        CancelToken? cancelToken,
+      }) async {
+    //参数
+    Map<String, String> params = {};
+    params['record_ids'] = recordIds ?? "";
+
+    if (Utils.isNotEmpty(reason)) {
+      params['audit_mark'] = reason ?? "";
+    }
+
+    final result = await httpProvider.requestNetResult(
+      ApiConstants.apiAttendanceReviewReject,
+      method: HttpMethod.POST,
+      params: params,
+      networkDebounce: true,
+      isShowLoadingDialog: true,
+      cancelToken: cancelToken,
+    );
+
+    //根据返回的结果,封装原始数据为Bean/Entity对象
+    if (result.isSuccess) {
+      //重新赋值data或list
+      return result.convert();
+    }
+    return result.convert();
+  }
+
 }

+ 2 - 1
packages/cs_router/lib/componentRouter/job_service.dart

@@ -1,5 +1,5 @@
 
-/**
+/*
  * Job组件对应的路由抽象接口
  */
 abstract class JobService {
@@ -8,4 +8,5 @@ abstract class JobService {
 
   void startJobListPage();
 
+  void startAttendanceReviewPage();
 }

+ 1 - 0
packages/cs_router/lib/path/router_path.dart

@@ -25,6 +25,7 @@ class RouterPath {
   static const jobAppliedStaffWorkflow = '/job/applied/workflow'; //指定工作中已申请的指定员工的信息与状态修改工作流
   static const jobAppliedStaffDetail = '/job/applied/staff/detail'; //工作中员工的详细信息
   static const jobAppliedStaffReviews = '/job/applied/staff/reviews'; //工作中员工的被评价列表
+  static const jobAttendanceReviewList = '/job/attendance/review/list'; //工作考勤的审核列表
 
   //用工请求
   static const jobLabourRequestList = '/labour/list'; //用工请求列表