Merge remote-tracking branch 'origin/design_3.0_TM_Module_snagsFix' into design_3.0_TM_Module_snagsFix
commit
d9f3a4e73a
@ -0,0 +1,291 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:test_sa/extensions/context_extension.dart';
|
||||
import 'package:test_sa/extensions/int_extensions.dart';
|
||||
import 'package:test_sa/extensions/string_extensions.dart';
|
||||
import 'package:test_sa/extensions/text_extensions.dart';
|
||||
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||
import 'package:test_sa/models/new_models/work_order_detail_model.dart';
|
||||
import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart';
|
||||
import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart';
|
||||
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
|
||||
import 'package:test_sa/views/widgets/date_and_time/date_picker.dart';
|
||||
import 'package:test_sa/views/widgets/status/report/service_report_assistant_employee_menu.dart';
|
||||
|
||||
class AssistantEmployeeList extends StatefulWidget {
|
||||
final List<AssistantEmployeesModel>? assistantEmployeeList;
|
||||
final ValueChanged<List<AssistantEmployeesModel>>? onListChanged;
|
||||
final double? cardPadding;
|
||||
final dynamic assetId;
|
||||
|
||||
const AssistantEmployeeList({
|
||||
super.key,
|
||||
this.assistantEmployeeList,
|
||||
this.onListChanged,
|
||||
required this.assetId,
|
||||
this.cardPadding,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AssistantEmployeeList> createState() => _AssistantEmployeeListState();
|
||||
}
|
||||
|
||||
class _AssistantEmployeeListState extends State<AssistantEmployeeList> {
|
||||
late List<AssistantEmployeesModel> _list;
|
||||
late List<TextEditingController> _controllers;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_list = List<AssistantEmployeesModel>.from(widget.assistantEmployeeList ?? []);
|
||||
_controllers = _list.map((e) => TextEditingController(text: e.workingHours?.toString() ?? '')).toList();
|
||||
}
|
||||
|
||||
void _addNewEntry() {
|
||||
setState(() {
|
||||
_list.add(AssistantEmployeesModel());
|
||||
_controllers.add(TextEditingController());
|
||||
});
|
||||
widget.onListChanged?.call(_list);
|
||||
}
|
||||
|
||||
void _removeEntry(int index) {
|
||||
setState(() {
|
||||
_list.removeAt(index);
|
||||
_controllers.removeAt(index);
|
||||
});
|
||||
widget.onListChanged?.call(_list);
|
||||
}
|
||||
|
||||
void _updateModel(int index, void Function(AssistantEmployeesModel model) updateList) {
|
||||
setState(() {
|
||||
updateList(_list[index]);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isReadOnly = Provider.of<ServiceRequestDetailProvider>(context, listen: false).isReadOnlyRequest;
|
||||
return ListView.builder(
|
||||
itemCount: _list.length + 1,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.all(widget.cardPadding ?? 16),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == _list.length) {
|
||||
return Visibility(
|
||||
visible: !isReadOnly,
|
||||
child: AppFilledButton(
|
||||
label: "Add Assistant Employee".addTranslation,
|
||||
maxWidth: true,
|
||||
textColor: AppColor.black10,
|
||||
buttonColor: context.isDark ? AppColor.neutral60 : AppColor.white10,
|
||||
icon: Icon(Icons.add_circle, color: AppColor.blueStatus(context)),
|
||||
showIcon: true,
|
||||
onPressed: _addNewEntry,
|
||||
),
|
||||
);
|
||||
}
|
||||
return EmployeeCard(
|
||||
model: _list[index],
|
||||
assetId: widget.assetId,
|
||||
index: index,
|
||||
isReadOnly: isReadOnly,
|
||||
onUpdate: (updateList) => _updateModel(index, updateList),
|
||||
onRemove: () => _removeEntry(index),
|
||||
workingHoursController: _controllers[index],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmployeeCard extends StatelessWidget {
|
||||
final AssistantEmployeesModel model;
|
||||
final int index;
|
||||
final bool isReadOnly;
|
||||
final dynamic assetId;
|
||||
|
||||
final void Function(void Function(AssistantEmployeesModel model)) onUpdate;
|
||||
final VoidCallback onRemove;
|
||||
final TextEditingController workingHoursController;
|
||||
|
||||
const EmployeeCard({
|
||||
super.key,
|
||||
required this.model,
|
||||
required this.assetId,
|
||||
required this.index,
|
||||
required this.isReadOnly,
|
||||
required this.onUpdate,
|
||||
required this.onRemove,
|
||||
required this.workingHoursController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final requestedDate = Provider.of<ServiceRequestDetailProvider>(context, listen: false).currentWorkOrder?.data?.requestedDate;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
context.translation.assistantEmployee.bodyText(context).custom(color: AppColor.black20),
|
||||
if (!isReadOnly)
|
||||
Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: "trash".toSvgAsset(height: 20, width: 20),
|
||||
).onPress(onRemove),
|
||||
],
|
||||
),
|
||||
8.height,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ServiceReportAssistantEmployeeMenu(
|
||||
title: context.translation.select,
|
||||
backgroundColor: AppColor.neutral100,
|
||||
assetId: assetId,
|
||||
initialValue: model.employee,
|
||||
onSelect: (employee) {
|
||||
if (employee != null) {
|
||||
onUpdate((model) {
|
||||
model.employee = employee.copyWith(id: 0);
|
||||
model.user = AssignedEmployee(
|
||||
userId: employee.user?.id,
|
||||
userName: employee.user?.name,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
8.height,
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ADatePicker(
|
||||
label: context.translation.startTime,
|
||||
hideShadow: true,
|
||||
backgroundColor: AppColor.neutral100,
|
||||
date: model.startDate,
|
||||
formatDateWithTime: true,
|
||||
from: requestedDate,
|
||||
onDatePicker: (selectedDate) {
|
||||
showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
).then((selectedTime) {
|
||||
if (selectedTime != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
selectedDate.year,
|
||||
selectedDate.month,
|
||||
selectedDate.day,
|
||||
selectedTime.hour,
|
||||
selectedTime.minute,
|
||||
);
|
||||
|
||||
if (requestedDate != null && selectedDateTime.isBefore(requestedDate)) {
|
||||
"Start time is before the request time.".showToast;
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDateTime.isAfter(DateTime.now())) {
|
||||
"Start time is after the current time".showToast;
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdate((model) {
|
||||
model.startDate = selectedDateTime;
|
||||
ServiceRequestUtils.calculateAndAssignWorkingHours(
|
||||
startTime: model.startDate,
|
||||
endTime: model.endDate,
|
||||
workingHoursController: workingHoursController,
|
||||
updateModel: (hours) => model.workingHours = hours,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
).expanded,
|
||||
8.width,
|
||||
ADatePicker(
|
||||
label: context.translation.endTime,
|
||||
hideShadow: true,
|
||||
backgroundColor: AppColor.neutral100,
|
||||
date: model.endDate,
|
||||
formatDateWithTime: true,
|
||||
from: requestedDate,
|
||||
onDatePicker: (selectedDate) {
|
||||
showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
).then((selectedTime) {
|
||||
if (selectedTime != null) {
|
||||
final endDateTime = DateTime(
|
||||
selectedDate.year,
|
||||
selectedDate.month,
|
||||
selectedDate.day,
|
||||
selectedTime.hour,
|
||||
selectedTime.minute,
|
||||
);
|
||||
|
||||
if (!endDateTime.isBefore(DateTime.now())) {
|
||||
"Please select a time before the current time.".showToast;
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.startDate == null || !endDateTime.isAfter(model.startDate!)) {
|
||||
"End date must be after start date".showToast;
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdate((model) {
|
||||
model.endDate = endDateTime;
|
||||
ServiceRequestUtils.calculateAndAssignWorkingHours(
|
||||
startTime: model.startDate,
|
||||
endTime: model.endDate,
|
||||
workingHoursController: workingHoursController,
|
||||
updateModel: (hours) => model.workingHours = hours,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
).expanded,
|
||||
],
|
||||
),
|
||||
8.height,
|
||||
AppTextFormField(
|
||||
labelText: context.translation.workingHours,
|
||||
backgroundColor: AppColor.neutral80,
|
||||
controller: workingHoursController,
|
||||
suffixIcon: "clock".toSvgAsset(width: 20, color: context.isDark ? AppColor.neutral10 : null).paddingOnly(end: 16),
|
||||
textAlign: TextAlign.center,
|
||||
enable: false,
|
||||
showShadow: false,
|
||||
labelStyle: AppTextStyles.textFieldLabelStyle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
8.height,
|
||||
AppTextFormField(
|
||||
initialValue: model.technicalComment,
|
||||
labelText: context.translation.technicalComment,
|
||||
backgroundColor: AppColor.neutral100,
|
||||
showShadow: false,
|
||||
labelStyle: AppTextStyles.textFieldLabelStyle,
|
||||
alignLabelWithHint: true,
|
||||
textInputType: TextInputType.multiline,
|
||||
onChange: (value) => onUpdate((model) => model.technicalComment = value),
|
||||
// onSaved: (value) => onUpdate((model) => model.technicalComment = value),
|
||||
),
|
||||
8.height,
|
||||
],
|
||||
)
|
||||
],
|
||||
).toShadowContainer(context, paddingObject: const EdgeInsets.symmetric(horizontal: 16, vertical: 12)).paddingOnly(bottom: 12);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue