Browse Source

create, update status task

bugfix/20250501
Đại Võ 1 year ago
parent
commit
365b57984a
23 changed files with 1093 additions and 11 deletions
  1. +1
    -1
      android/app/src/main/AndroidManifest.xml
  2. +6
    -6
      ios/Runner.xcodeproj/project.pbxproj
  3. +76
    -0
      lib/data/repository/repository.dart
  4. +6
    -0
      lib/main.dart
  5. +64
    -0
      lib/presentation/custom_widgets/checkbox/checkbox_widget.dart
  6. +1
    -1
      lib/presentation/screens/plot/widget_search.dart
  7. +6
    -2
      lib/presentation/screens/plot_detail/widget_tab.dart
  8. +129
    -0
      lib/presentation/screens/task/bloc/task_bloc.dart
  9. +17
    -0
      lib/presentation/screens/task/bloc/task_event.dart
  10. +33
    -0
      lib/presentation/screens/task/bloc/task_state.dart
  11. +175
    -0
      lib/presentation/screens/task/create_task_page.dart
  12. +87
    -0
      lib/presentation/screens/task/cubit/create_task_cubit.dart
  13. +22
    -0
      lib/presentation/screens/task/cubit/create_task_state.dart
  14. +21
    -0
      lib/presentation/screens/task/models/employee.dart
  15. +72
    -0
      lib/presentation/screens/task/models/task.dart
  16. +18
    -0
      lib/presentation/screens/task/models/task_filter_request.dart
  17. +33
    -0
      lib/presentation/screens/task/models/task_request.dart
  18. +18
    -0
      lib/presentation/screens/task/models/task_update_request.dart
  19. +193
    -0
      lib/presentation/screens/task/task_page.dart
  20. +94
    -0
      lib/presentation/screens/task/widgets/task_item.dart
  21. +5
    -0
      lib/utils/const_common.dart
  22. +15
    -0
      lib/utils/helpers.dart
  23. +1
    -1
      pubspec.yaml

+ 1
- 1
android/app/src/main/AndroidManifest.xml View File

@@ -62,7 +62,7 @@
android:value="2" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="au.com.homecarenet.caregiver.fileprovider"
android:authorities="vn.azteam.farmdemo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

+ 6
- 6
ios/Runner.xcodeproj/project.pbxproj View File

@@ -369,7 +369,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 22;
DEVELOPMENT_TEAM = C3DTD2JH94;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -385,7 +385,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 1.1.5;
MARKETING_VERSION = 1.1.6;
PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -511,7 +511,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 22;
DEVELOPMENT_TEAM = C3DTD2JH94;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -527,7 +527,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 1.1.5;
MARKETING_VERSION = 1.1.6;
PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -547,7 +547,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 22;
DEVELOPMENT_TEAM = C3DTD2JH94;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -563,7 +563,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 1.1.5;
MARKETING_VERSION = 1.1.6;
PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

+ 76
- 0
lib/data/repository/repository.dart View File

@@ -25,12 +25,17 @@ import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart';
import 'package:farm_tpf/presentation/screens/codes/models/stamp_filter_request.dart';
import 'package:farm_tpf/presentation/screens/codes/models/stamp_request.dart';
import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart';
import 'package:farm_tpf/presentation/screens/task/models/employee.dart';
import 'package:farm_tpf/presentation/screens/task/models/task_request.dart';
import 'package:farm_tpf/utils/const_common.dart';
import 'package:flutter/material.dart';

import '../../presentation/screens/codes/models/stamp_type.dart';
import '../../presentation/screens/login/models/request_user.dart';
import '../../presentation/screens/login/models/response_user.dart';
import '../../presentation/screens/task/models/task.dart';
import '../../presentation/screens/task/models/task_filter_request.dart';
import '../../presentation/screens/task/models/task_update_request.dart';
import '../api/app_exception.dart';

class Repository {
@@ -442,4 +447,75 @@ class Repository {
rethrow;
}
}

// Task
Future<List<Task>> tasks({
int page = 0,
int size = 20,
required TaskFilterRequest filter,
}) async {
try {
// var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size&sort=createdDate,${filter.sort ?? 'asc'}';
var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size';
var res = await dio.post(url, data: {
// 'status': filter.status,
});

return (res.data as List).map((e) => Task.fromJson(e)).toList();
} catch (e) {
rethrow;
}
}

Future<void> createTask(
Function(dynamic) onSuccess,
Function(String) onError, {
required RequestTask item,
}) async {
try {
var url = '${ConstCommon.baseUrl}/api/tb-todo-lists';

await dio.post(url, data: item).then(
(value) {
onSuccess(value);
},
).catchError((e) {
onError(AppException.handleError(e));
});
} catch (e) {
onError(AppException.handleError(e));
}
}

Future<void> updateTask(
Function(dynamic) onSuccess,
Function(String) onError, {
required RequestTaskUpdate item,
}) async {
try {
var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/';

await dio.put(url, data: item).then(
(value) {
onSuccess(value);
},
).catchError((e) {
onError(AppException.handleError(e));
});
} catch (e) {
onError(AppException.handleError(e));
}
}

Future<List<Employee>> getEmployees() async {
try {
var url = '${ConstCommon.baseUrl}/api/get-all-users-by-login-info';
var res = await dio.get(
url,
);
return (res.data as List).map((e) => Employee.fromJson(e)).toList();
} catch (e) {
rethrow;
}
}
}

+ 6
- 0
lib/main.dart View File

@@ -14,6 +14,7 @@ import 'app.dart';
// import 'data/repository/auth_repository.dart';
import 'data/repository/authentication_repository.dart';
import 'environment/app_config.dart';
import 'presentation/screens/task/bloc/task_bloc.dart';

final GlobalKey<NavigatorState> globalNavigator = GlobalKey<NavigatorState>();
Future<void> main() async {
@@ -46,6 +47,11 @@ Future<void> main() async {
Repository(),
),
),
BlocProvider(
create: (_) => TaskBloc(
Repository(),
),
),
BlocProvider(
create: (_) => DetailStampCubit(),
),

+ 64
- 0
lib/presentation/custom_widgets/checkbox/checkbox_widget.dart View File

@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import '../../../themes/app_dimension.dart';

import '../../../themes/app_colors.dart';
import '../../../themes/styles_text.dart';

class CheckboxWidget extends StatefulWidget {
final String title;
final bool isChecked;
final Function(bool) onChange;
final TextStyle? style;
const CheckboxWidget({
Key? key,
required this.title,
required this.isChecked,
required this.onChange,
this.style,
}) : super(key: key);

@override
_CheckboxWidgetState createState() => _CheckboxWidgetState();
}

class _CheckboxWidgetState extends State<CheckboxWidget> {
var checked = false;

@override
void initState() {
super.initState();
checked = widget.isChecked;
}

@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
checked = !checked;
widget.onChange(checked);
});
},
child: Row(
children: [
checked
? Icon(
Icons.check_box,
color: AppColors.primary1,
)
: Icon(
Icons.check_box_outline_blank,
color: AppColors.neutral1,
),
SizedBox(
width: 8.w,
),
Text(
'${widget.title}',
style: widget.style ?? StylesText.body1,
),
],
),
);
}
}

+ 1
- 1
lib/presentation/screens/plot/widget_search.dart View File

@@ -56,7 +56,7 @@ class _WidgetSearchState extends State<WidgetSearch> {
widget.onPressed(widget.searchController.text);
},
),
hintText: 'Tìm theo mã, tên lô',
hintText: 'Tìm kiếm ...',
hintStyle: TextStyle(color: Colors.grey[500])),
onSubmitted: (value) {
widget.onPressed(widget.searchController.text);

+ 6
- 2
lib/presentation/screens/plot_detail/widget_tab.dart View File

@@ -1,5 +1,6 @@
import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_history.dart';
import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_parameter.dart';
import 'package:farm_tpf/presentation/screens/task/task_page.dart';
import 'package:farm_tpf/utils/const_color.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -69,7 +70,7 @@ class _HomeTabbarWidgetState extends State<HomeTabbarWidget> with TickerProvider
Container(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: Text(
'Chỉ số',
'Công việc',
style: TextStyle(color: selectedTab.index == 0 ? AppColors.DEFAULT : Colors.black, fontSize: 12),
),
),
@@ -93,7 +94,10 @@ class _HomeTabbarWidgetState extends State<HomeTabbarWidget> with TickerProvider
body: TabBarView(
controller: tabbarController,
children: [
PlotParameterScreen(
// PlotParameterScreen(
// cropId: widget.cropId ?? -1,
// ),
TaskPage(
cropId: widget.cropId ?? -1,
),
PlotInformationScreen(

+ 129
- 0
lib/presentation/screens/task/bloc/task_bloc.dart View File

@@ -0,0 +1,129 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:farm_tpf/presentation/screens/task/models/task_update_request.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../../../../data/api/app_exception.dart';
import '../../../../data/repository/repository.dart';
import '../../../../models/item_dropdown.dart';
import '../../../../utils/const_common.dart';
import '../../../../utils/helpers.dart';
import '../../../../utils/utils.dart';
import '../../../custom_widgets/widget_utils.dart';
import '../models/task.dart';
import '../models/task_filter_request.dart';

part 'task_event.dart';
part 'task_state.dart';

class TaskBloc extends Bloc<TaskEvent, TaskState> {
final Repository repository;
int pageSize = 20;
TaskBloc(this.repository) : super(TaskInitial());
var status = ValueNotifier(
TaskStatus.values
.map(
(e) => ItemDropDown(key: describeEnum(e), value: Helpers.getTaskStatus(describeEnum(e))),
)
.toList(),
);

var selectedStatus = ValueNotifier(
TaskStatus.values
.map(
(e) => ItemDropDown(key: describeEnum(e), value: Helpers.getTaskStatus(describeEnum(e))),
)
.toList(),
);
var sort = ValueNotifier(describeEnum(SortType.asc));

@override
Stream<TaskState> mapEventToState(
TaskEvent event,
) async* {
if (event is DataFetched && !(state is TaskSuccess && ((state as TaskSuccess).hasReachedMax ?? false))) {
try {
if (state is TaskInitial) {
yield (TaskLoading());
final response = await getListTask(0);
yield TaskSuccess(
items: response,
page: 0,
hasReachedMax: response.length < pageSize ? true : false,
);
}
if (state is TaskSuccess) {
final currentState = state as TaskSuccess;
if (currentState.hasReachedMax ?? false) {
return;
}
int page = (currentState.page ?? 0) + 1;
final response = await getListTask(page);
yield response.isEmpty
? currentState.copyWith(hasReachedMax: true)
: TaskSuccess(
items: (currentState.items ?? []) + response,
page: (currentState.page ?? 0) + 1,
hasReachedMax: false,
);
}
} catch (e) {
var errorString = AppException.handleError(e);
yield (TaskFailure(errorString: errorString));
}
}
if (event is OnRefresh) {
try {
yield (TaskLoading());
final response = await getListTask(0);
var items = <Task>[];
response.forEach((element) {
items.add(Task.clone(element));
});
yield TaskSuccess(
items: items,
page: 0,
hasReachedMax: items.length < pageSize ? true : false,
);
} catch (e) {
yield (TaskFailure(errorString: AppException.handleError(e)));
}
} else if (event is OnSearch) {
try {
yield (TaskLoading());
final response = await getListTask(0);
yield TaskSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false);
} catch (e) {
yield (TaskFailure(errorString: AppException.handleError(e)));
}
}
}

Future<List<Task>> getListTask(int page) async {
var filter = TaskFilterRequest()
..sort = sort.value
..status = selectedStatus.value.map((e) => e.key ?? '').toList();
return await repository.tasks(page: 0, filter: filter);
}

Future<void> updateStatusTask(
RequestTaskUpdate task, {
required Function onSuccess,
}) async {
print(task.toJson());
UtilWidget.showLoading();
await repository.updateTask(
(success) {
UtilWidget.hideDialog();
Utils.showSnackBarSuccess();
onSuccess();
},
(errorMessage) {
UtilWidget.hideDialog();
Utils.showSnackBarError();
},
item: task,
);
}
}

+ 17
- 0
lib/presentation/screens/task/bloc/task_event.dart View File

@@ -0,0 +1,17 @@
part of 'task_bloc.dart';

abstract class TaskEvent extends Equatable {
const TaskEvent();

@override
List<Object> get props => [];
}

class DataFetched extends TaskEvent {}

class OnRefresh extends TaskEvent {}

class OnSearch extends TaskEvent {
final String searchString;
OnSearch({required this.searchString});
}

+ 33
- 0
lib/presentation/screens/task/bloc/task_state.dart View File

@@ -0,0 +1,33 @@
part of 'task_bloc.dart';

abstract class TaskState extends Equatable {
const TaskState();

@override
List<Object> get props => [];
}

class TaskInitial extends TaskState {}

class TaskLoading extends TaskState {}

class TaskFailure extends TaskState {
final String errorString;
TaskFailure({required this.errorString});
}

class TaskSuccess<Task> extends TaskState {
final List<Task>? items;
final int? page;
final bool? hasReachedMax;

const TaskSuccess({this.items, this.page, this.hasReachedMax});

TaskSuccess copyWith({List<Task>? items, int? page, bool? hasReachedMax}) {
return TaskSuccess(
items: items ?? this.items,
page: page ?? this.page,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}
}

+ 175
- 0
lib/presentation/screens/task/create_task_page.dart View File

@@ -0,0 +1,175 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:farm_tpf/models/item_dropdown.dart';
import 'package:farm_tpf/presentation/screens/codes/models/stamp_type.dart';
import 'package:farm_tpf/presentation/screens/codes/widgets/item_column.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';

import '../../../utils/utils.dart';
import '../../custom_widgets/app_bar_widget.dart';
import '../../custom_widgets/button_widget.dart';
import '../../custom_widgets/date_picker/date_picker_widget.dart';
import '../../custom_widgets/dropdown/dropdown_bottom_sheet.dart';
import '../../custom_widgets/textfield/text_field_normal.dart';
import 'cubit/create_task_cubit.dart';

class CreateTaskPage extends StatefulWidget {
final int cropId;
const CreateTaskPage({
super.key,
required this.cropId,
});

@override
State<CreateTaskPage> createState() => _CreateTaskPageState();
}

class _CreateTaskPageState extends State<CreateTaskPage> {
final bloc = CreateTaskCubit();

@override
void initState() {
super.initState();
bloc.preparedData(cropId: widget.cropId);
}

@override
void dispose() {
bloc.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBarWidget(),
body: BlocListener<CreateTaskCubit, CreateTaskState>(
bloc: bloc,
listener: ((context, state) {
if (state is CreateTaskLoading) {
SchedulerBinding.instance.addPostFrameCallback((timeTask) {
UtilWidget.showLoading();
});
} else if (state is CreateTaskFailure) {
SchedulerBinding.instance.addPostFrameCallback((timeTask) {
UtilWidget.hideLoading();
// UtilWidget.showToastError(state.errorMessage);
});
} else if (state is CreateTaskPrepareDataSuccessful) {
SchedulerBinding.instance.addPostFrameCallback((timeTask) {
UtilWidget.hideLoading();
});
}
}),
child: KeyboardDismisser(
child: Container(
child: Form(
key: bloc.formKey,
child: Column(
children: [
Expanded(
child: _widgetBody(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ButtonWidget(
title: 'Cập nhật',
onPressed: () {
bloc.onSubmit(widget.cropId);
},
),
),
],
),
),
),
),
),
);
}

Widget _widgetBody() {
return Container(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ItemColumnWidget(
title: 'Giao việc cho',
child: ValueListenableBuilder<String>(
valueListenable: bloc.selectedPEmployee,
builder: (context, selected, _) {
return ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.employees,
builder: (context, employees, _) {
return DropdownBottomSheet(
dataSources: employees,
initValue: selected,
onSelected: (val) {
bloc.selectedPEmployee.value = val.key ?? '';
},
hint: 'Giao việc cho',
);
},
);
},
),
),
const SizedBox(
height: 8,
),
ItemColumnWidget(
title: 'Tiêu đề',
child: TextFieldNormal(
controller: bloc.titleNameCtl,
maxLines: 1,
hint: 'Tiêu đề',
),
),
const SizedBox(
height: 8,
),
ItemColumnWidget(
title: 'Mô tả công việc',
child: TextFieldNormal(
controller: bloc.detailCtl,
maxLines: 3,
hint: 'Mô tả công việc',
),
),
const SizedBox(
height: 8,
),
const SizedBox(height: 8),
ValueListenableBuilder<DateTime>(
valueListenable: bloc.deadline,
builder: (context, dexuat, _) {
return ItemColumnWidget(
title: 'Hạn chót',
child: DatePickerWidget(
initDateTime: dexuat,
onUpdateDateTime: (selectedDate) {
if (selectedDate != null) {
bloc.deadline.value = selectedDate;
}
},
),
);
},
),
const SizedBox(
height: 16,
),
],
),
),
);
}
}

+ 87
- 0
lib/presentation/screens/task/cubit/create_task_cubit.dart View File

@@ -0,0 +1,87 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:farm_tpf/presentation/screens/task/models/employee.dart';
import 'package:farm_tpf/utils/formatter.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../../../data/api/app_exception.dart';
import '../../../../data/repository/repository.dart';
import '../../../../models/item_dropdown.dart';
import '../../../../utils/utils.dart';
import '../../../custom_widgets/widget_utils.dart';
import '../models/task_request.dart';

part 'create_task_state.dart';

class CreateTaskCubit extends Cubit<CreateTaskState> {
CreateTaskCubit() : super(CreateTaskInitial());
final repository = Repository();
final formKey = GlobalKey<FormState>();
final titleNameCtl = TextEditingController();
final detailCtl = TextEditingController();
var deadline = ValueNotifier(DateTime.now());

var employeeRaws = <Employee>[];
var employees = ValueNotifier(<ItemDropDown>[]);
var selectedPEmployee = ValueNotifier('');
// var existedCreateTask = UpdateCreateTask();

void dispose() {
titleNameCtl.dispose();
detailCtl.dispose();
}

Future<void> preparedData({required int cropId}) async {
try {
await Future.delayed(const Duration(seconds: 0));
emit(CreateTaskLoading());

employeeRaws = await repository.getEmployees();
employees.value = employeeRaws
.map(
(e) => ItemDropDown(key: e.id?.toString(), value: e.name),
)
.toList();
emit(CreateTaskPrepareDataSuccessful());
} catch (e) {
emit(CreateTaskFailure(AppException.handleError(e)));
}
}

Future<void> onSubmit(int cropId) async {
if (formKey.currentState!.validate()) {
if (selectedPEmployee.value.isEmpty) {
Utils.showSnackBarWarning(message: 'Vui lòng chọn nhân viên');
return;
} else if (titleNameCtl.text.trim().isEmpty) {
Utils.showSnackBarWarning(message: 'Vui lòng nhập tiêu đề');
return;
} else if (detailCtl.text.trim().isEmpty) {
Utils.showSnackBarWarning(message: 'Vui lòng nhập nội dung');
return;
}
var requestTask = RequestTask();
requestTask
..cropId = cropId
..title = titleNameCtl.text
..detail = detailCtl.text
..userAssignedId = int.tryParse(selectedPEmployee.value)
..deadline = deadline.value.convertLocalDateTimeToStringUtcDateTime();
print(requestTask.toJson());
UtilWidget.showLoading();
await repository.createTask(
(success) {
UtilWidget.hideDialog();
Get.back(result: 'ok');
Utils.showSnackBarSuccess();
},
(errorMessage) {
UtilWidget.hideDialog();
Utils.showSnackBarError();
},
item: requestTask,
);
}
}
}

+ 22
- 0
lib/presentation/screens/task/cubit/create_task_state.dart View File

@@ -0,0 +1,22 @@
part of 'create_task_cubit.dart';

abstract class CreateTaskState extends Equatable {
const CreateTaskState();

@override
List<Object> get props => [];
}

class CreateTaskInitial extends CreateTaskState {}

class CreateTaskLoading extends CreateTaskState {}

class CreateTaskFailure extends CreateTaskState {
final String errorMessage;

CreateTaskFailure(this.errorMessage);
}

class CreateTaskPrepareDataSuccessful extends CreateTaskState {
CreateTaskPrepareDataSuccessful();
}

+ 21
- 0
lib/presentation/screens/task/models/employee.dart View File

@@ -0,0 +1,21 @@
class Employee {
int? id;
String? name;

Employee({
this.id,
this.name,
});

Employee.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['fullName'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['fullName'] = this.name;
return data;
}
}

+ 72
- 0
lib/presentation/screens/task/models/task.dart View File

@@ -0,0 +1,72 @@
class Task {
int? id;
String? title;
String? description;
String? dueDate;
String? executeDate;
Assigned? assigned;
bool? isCompleted;

Task({
this.id,
this.title,
this.description,
this.dueDate,
this.executeDate,
this.assigned,
this.isCompleted,
});

Task.fromJson(Map<String, dynamic> json) {
title = json['title'];
id = json['id'];
description = json['detail'];
dueDate = json['deadline'];
executeDate = json['executeDate'];
assigned = json['assigned'] != null ? new Assigned.fromJson(json['assigned']) : null;
isCompleted = json['completed'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['detail'] = this.description;
data['deadline'] = this.dueDate;
data['executeDate'] = this.executeDate;
if (this.assigned != null) {
data['assigned'] = this.assigned?.toJson();
}
data['completed'] = this.isCompleted;
return data;
}

Task.clone(Task task) {
this.id = task.id;
this.title = task.title;
this.description = task.description;
this.dueDate = task.dueDate;
this.executeDate = task.executeDate;
this.assigned = task.assigned;
this.isCompleted = task.isCompleted;
}
}

class Assigned {
int? id;
bool? activated;

Assigned({this.id, this.activated});

Assigned.fromJson(Map<String, dynamic> json) {
id = json['id'];
activated = json['activated'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['activated'] = this.activated;
return data;
}
}

+ 18
- 0
lib/presentation/screens/task/models/task_filter_request.dart View File

@@ -0,0 +1,18 @@
class TaskFilterRequest {
List<String>? status;
String? sort;

TaskFilterRequest({this.status, this.sort});

TaskFilterRequest.fromJson(Map<String, dynamic> json) {
status = json['status'].cast<String>();
sort = json['sort'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['sort'] = this.sort;
return data;
}
}

+ 33
- 0
lib/presentation/screens/task/models/task_request.dart View File

@@ -0,0 +1,33 @@
class RequestTask {
String? title;
String? detail;
String? deadline;
int? userAssignedId;
int? cropId;

RequestTask({
this.title,
this.detail,
this.deadline,
this.userAssignedId,
this.cropId,
});

RequestTask.fromJson(Map<String, dynamic> json) {
title = json['title'];
detail = json['detail'];
deadline = json['deadline'];
userAssignedId = json['user_assigned_id'];
cropId = json['crop_id'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['title'] = this.title;
data['detail'] = this.detail;
data['deadline'] = this.deadline;
data['user_assigned_id'] = this.userAssignedId;
data['crop_id'] = this.cropId;
return data;
}
}

+ 18
- 0
lib/presentation/screens/task/models/task_update_request.dart View File

@@ -0,0 +1,18 @@
class RequestTaskUpdate {
int? id;
bool? completed;

RequestTaskUpdate({this.id, this.completed});

RequestTaskUpdate.fromJson(Map<String, dynamic> json) {
id = json['id'];
completed = json['completed'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['completed'] = this.completed;
return data;
}
}

+ 193
- 0
lib/presentation/screens/task/task_page.dart View File

@@ -0,0 +1,193 @@
import 'package:farm_tpf/data/repository/repository.dart';
import 'package:farm_tpf/presentation/custom_widgets/button/button_2_icon.dart';
import 'package:farm_tpf/presentation/custom_widgets/button/second_button.dart';
import 'package:farm_tpf/presentation/screens/codes/code_detail_page.dart';
import 'package:farm_tpf/presentation/screens/codes/create_stamp_page.dart';
import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart';
import 'package:farm_tpf/presentation/screens/codes/widgets/item_code.dart';
import 'package:farm_tpf/presentation/screens/task/models/task_update_request.dart';
import 'package:farm_tpf/themes/app_colors.dart';
import 'package:farm_tpf/utils/const_common.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get.dart';

import '../../../models/item_dropdown.dart';
import '../../../themes/styles_text.dart';
import '../../../utils/const_string.dart';
import '../../../utils/helpers.dart';
import '../../custom_widgets/bottom_loader.dart';
import '../../custom_widgets/dropdown/multiple_select_bottom_sheet.dart';
import '../../custom_widgets/loading_list_page.dart';
import '../plot/widget_search.dart';
import 'bloc/task_bloc.dart';
import 'create_task_page.dart';
import 'widgets/task_item.dart';

class TaskPage extends StatefulWidget {
final int cropId;
const TaskPage({super.key, required this.cropId});

@override
State<TaskPage> createState() => _TaskPageState();
}

class _TaskPageState extends State<TaskPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
TaskBloc bloc = TaskBloc(Repository());
final _scrollController = ScrollController();
final _scrollThreshold = 250.0;

@override
void initState() {
bloc.add(DataFetched());

_scrollController.addListener(() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll < _scrollThreshold) {
bloc.add(DataFetched());
}
});
super.initState();
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
key: _scaffoldKey,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: [
ValueListenableBuilder<String>(
valueListenable: bloc.sort,
builder: (context, sort, _) {
return Button2Icon(
leftIcon: (sort == describeEnum(SortType.asc)) ? CupertinoIcons.arrow_up : CupertinoIcons.arrow_down,
title: 'Ngày tạo',
onPressed: () {
if (sort == describeEnum(SortType.asc)) {
bloc.sort.value = describeEnum(SortType.desc);
} else {
bloc.sort.value = describeEnum(SortType.asc);
}
bloc.sort.notifyListeners();
bloc.add(OnRefresh());
},
);
},
),
ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.selectedStatus,
builder: (context, selecteds, _) {
return ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.status,
builder: (context, status, _) {
return MultipleSelectBottomSheet(
dataSources: status,
initValue: selecteds,
onSelected: (val) {
bloc.selectedStatus.value = val;
Helpers.hideKeyboard(context);
bloc.add(OnRefresh());
},
hint: 'Trạng thái',
);
},
);
},
),
const Spacer(),
SecondButton(
onPressed: () {
Get.to(() => CreateTaskPage(
cropId: widget.cropId,
))?.then((value) {
if (value != null) {
bloc.add(OnRefresh());
}
});
},
title: 'Thêm',
leftIcon: CupertinoIcons.add,
color: AppColors.primary1,
textColor: Colors.white,
borderColor: AppColors.primary1,
),
],
),
Expanded(
child: mainBody(),
),
],
),
),
);
}

Widget mainBody() {
return BlocBuilder<TaskBloc, TaskState>(
bloc: bloc,
builder: (context, state) {
if (state is TaskFailure) {
return Center(child: Text(state.errorString));
}
if (state is TaskSuccess) {
if ((state.items ?? []).isEmpty) {
return Center(child: Text(label_list_empty));
}
return RefreshIndicator(
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return index >= (state.items ?? []).length
? BottomLoader()
: ItemTask(
item: state.items?[index],
onPressed: () {
// Get.to(
// () => TaskDetailPage(
// stampId: state.items?[index].id,
// stampTask: state.items?[index].code,
// ),
// );
},
onChangedStatus: (status) {
bloc.updateStatusTask(
RequestTaskUpdate(
id: state.items?[index].id,
completed: status,
),
onSuccess: () {
bloc.add(OnRefresh());
},
);
},
);
},
itemCount: (state.hasReachedMax ?? false) ? (state.items ?? []).length : (state.items ?? []).length + 1,
controller: _scrollController,
),
onRefresh: () async {
bloc.add(OnRefresh());
});
}
return Center(
child: LoadingListPage(),
);
},
);
}
}

+ 94
- 0
lib/presentation/screens/task/widgets/task_item.dart View File

@@ -0,0 +1,94 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:farm_tpf/presentation/custom_widgets/checkbox/checkbox_widget.dart';
import 'package:farm_tpf/utils/helpers.dart';
import 'package:flutter/material.dart';

import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart';

import '../../../../themes/app_colors.dart';
import '../../../../themes/styles_text.dart';
import 'package:farm_tpf/utils/formatter.dart';

import '../models/task.dart';

class ItemTask extends StatelessWidget {
final Task item;
final Function onPressed;
final Function(bool) onChangedStatus;
ItemTask({
Key? key,
required this.item,
required this.onPressed,
required this.onChangedStatus,
}) : super(key: key);

@override
Widget build(BuildContext context) {
var dueDate = item.dueDate?.format_DDMMYY().toString() ?? '';
var completedDate = item.executeDate?.format_DDMMYY().toString() ?? '';
var backgroundColor = Colors.white;
if (item.isCompleted ?? false) {
backgroundColor = AppColors.primary1.withOpacity(0.3);
} else {
var dueDateCompare = item.dueDate?.convertStringServerDateTimeToLocalDateTime() ?? DateTime.now();
if (Helpers.isAfterToday(dueDateCompare)) {
backgroundColor = Colors.white;
} else {
backgroundColor = AppColors.semantic6;
}
}
return GestureDetector(
onTap: () {
onPressed();
},
child: Container(
padding: EdgeInsets.all(8),
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
color: backgroundColor,
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${item.title ?? ''}',
style: StylesText.body4,
),
const SizedBox(
height: 4,
),
Text(
'Hạn chót: $dueDate',
style: StylesText.body6,
),
const SizedBox(
height: 4,
),
Text(
'Hoàn thành: $completedDate',
style: StylesText.body6,
),
],
),
),
CheckboxWidget(
title: '',
isChecked: item.isCompleted ?? false,
onChange: (val) {
onChangedStatus(val);
},
)
],
),
),
);
}
}

+ 5
- 0
lib/utils/const_common.dart View File

@@ -34,3 +34,8 @@ enum SortType {
asc,
desc,
}

enum TaskStatus {
completed,
no_completed,
}

+ 15
- 0
lib/utils/helpers.dart View File

@@ -44,4 +44,19 @@ class Helpers {
}
return '';
}

static String getTaskStatus(String status) {
if (status.toLowerCase() == describeEnum(TaskStatus.completed)) {
return 'Hoàn thành';
} else if (status.toLowerCase() == describeEnum(TaskStatus.no_completed)) {
return 'Chưa hoàn thành';
}
return '';
}

static bool isAfterToday(DateTime compareDate) {
var now = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day);
var date = DateTime(compareDate.year, compareDate.month, compareDate.day);
return date.isAfter(now);
}
}

+ 1
- 1
pubspec.yaml View File

@@ -2,7 +2,7 @@ name: farm_tpf
description: A new Flutter project.

publish_to: 'none'
version: 1.1.6+21
version: 1.1.7+23

environment:
sdk: ">=2.17.0 <=3.0.0"

Loading…
Cancel
Save