Merge pull request 'Manish_Dev_13/12' (#93) from Manish_Dev_13/12 into Decription_Feature
Reviewed-on: #93
This commit is contained in:
commit
ec6122a5a5
@ -12,7 +12,8 @@ class DailyTaskPlanningController extends GetxController {
|
|||||||
RxList<EmployeeModel> employees = <EmployeeModel>[].obs;
|
RxList<EmployeeModel> employees = <EmployeeModel>[].obs;
|
||||||
RxList<EmployeeModel> selectedEmployees = <EmployeeModel>[].obs;
|
RxList<EmployeeModel> selectedEmployees = <EmployeeModel>[].obs;
|
||||||
List<EmployeeModel> allEmployeesCache = [];
|
List<EmployeeModel> allEmployeesCache = [];
|
||||||
List<TaskPlanningDetailsModel> dailyTasks = [];
|
RxList<TaskPlanningDetailsModel> dailyTasks =
|
||||||
|
<TaskPlanningDetailsModel>[].obs;
|
||||||
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
||||||
|
|
||||||
MyFormValidator basicValidator = MyFormValidator();
|
MyFormValidator basicValidator = MyFormValidator();
|
||||||
@ -97,7 +98,6 @@ class DailyTaskPlanningController extends GetxController {
|
|||||||
if (response == true) {
|
if (response == true) {
|
||||||
logSafe("Task assigned successfully", level: LogLevel.info);
|
logSafe("Task assigned successfully", level: LogLevel.info);
|
||||||
await fetchBuildingInfra(buildingId, projectId, serviceId);
|
await fetchBuildingInfra(buildingId, projectId, serviceId);
|
||||||
Get.back();
|
|
||||||
|
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Success",
|
title: "Success",
|
||||||
@ -129,18 +129,17 @@ class DailyTaskPlanningController extends GetxController {
|
|||||||
final infraData = infraResponse?['data'] as List<dynamic>?;
|
final infraData = infraResponse?['data'] as List<dynamic>?;
|
||||||
|
|
||||||
if (infraData == null || infraData.isEmpty) {
|
if (infraData == null || infraData.isEmpty) {
|
||||||
dailyTasks = [];
|
dailyTasks.clear(); //reactive clear
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter buildings with 0 planned & completed work
|
|
||||||
final filteredBuildings = infraData.where((b) {
|
final filteredBuildings = infraData.where((b) {
|
||||||
final planned = (b['plannedWork'] as num?)?.toDouble() ?? 0;
|
final planned = (b['plannedWork'] as num?)?.toDouble() ?? 0;
|
||||||
final completed = (b['completedWork'] as num?)?.toDouble() ?? 0;
|
final completed = (b['completedWork'] as num?)?.toDouble() ?? 0;
|
||||||
return planned > 0 || completed > 0;
|
return planned > 0 || completed > 0;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
dailyTasks = filteredBuildings.map((buildingJson) {
|
final mapped = filteredBuildings.map((buildingJson) {
|
||||||
final building = Building(
|
final building = Building(
|
||||||
id: buildingJson['id'],
|
id: buildingJson['id'],
|
||||||
name: buildingJson['buildingName'],
|
name: buildingJson['buildingName'],
|
||||||
@ -163,14 +162,19 @@ class DailyTaskPlanningController extends GetxController {
|
|||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
dailyTasks.assignAll(mapped);
|
||||||
|
|
||||||
buildingLoadingStates.clear();
|
buildingLoadingStates.clear();
|
||||||
buildingsWithDetails.clear();
|
buildingsWithDetails.clear();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
logSafe("Error fetching daily task data",
|
logSafe(
|
||||||
level: LogLevel.error, error: e, stackTrace: stack);
|
"Error fetching daily task data",
|
||||||
|
level: LogLevel.error,
|
||||||
|
error: e,
|
||||||
|
stackTrace: stack,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isFetchingTasks.value = false;
|
isFetchingTasks.value = false;
|
||||||
update(); // dailyTasks is non-reactive
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
class ApiEndpoints {
|
class ApiEndpoints {
|
||||||
// static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
||||||
// static const String baseUrl = "https://api.marcoaiot.com/api";
|
// static const String baseUrl = "https://api.marcoaiot.com/api";
|
||||||
// static const String baseUrl = "https://devapi.marcoaiot.com/api";
|
// static const String baseUrl = "https://devapi.marcoaiot.com/api";
|
||||||
// static const String baseUrl = "https://mapi.marcoaiot.com/api";
|
// static const String baseUrl = "https://mapi.marcoaiot.com/api";
|
||||||
static const String baseUrl = "https://api.onfieldwork.com/api";
|
// static const String baseUrl = "https://api.onfieldwork.com/api";
|
||||||
|
|
||||||
|
|
||||||
static const String getMasterCurrencies = "/Master/currencies/list";
|
static const String getMasterCurrencies = "/Master/currencies/list";
|
||||||
static const String getMasterExpensesCategories =
|
static const String getMasterExpensesCategories =
|
||||||
@ -48,7 +47,8 @@ class ApiEndpoints {
|
|||||||
static const String getProjects = "/project/list";
|
static const String getProjects = "/project/list";
|
||||||
static const String getGlobalProjects = "/project/list/basic";
|
static const String getGlobalProjects = "/project/list/basic";
|
||||||
static const String getTodaysAttendance = "/attendance/project/team";
|
static const String getTodaysAttendance = "/attendance/project/team";
|
||||||
static const String getAttendanceForDashboard = "/dashboard/get/attendance/employee/:projectId";
|
static const String getAttendanceForDashboard =
|
||||||
|
"/dashboard/get/attendance/employee/:projectId";
|
||||||
static const String getAttendanceLogs = "/attendance/project/log";
|
static const String getAttendanceLogs = "/attendance/project/log";
|
||||||
static const String getAttendanceLogView = "/attendance/log/attendance";
|
static const String getAttendanceLogView = "/attendance/log/attendance";
|
||||||
static const String getRegularizationLogs = "/attendance/regularize";
|
static const String getRegularizationLogs = "/attendance/regularize";
|
||||||
@ -142,7 +142,6 @@ class ApiEndpoints {
|
|||||||
static const String manageOrganizationHierarchy =
|
static const String manageOrganizationHierarchy =
|
||||||
"/organization/hierarchy/manage";
|
"/organization/hierarchy/manage";
|
||||||
|
|
||||||
|
|
||||||
// Service Project Module API Endpoints
|
// Service Project Module API Endpoints
|
||||||
static const String getServiceProjectsList = "/serviceproject/list";
|
static const String getServiceProjectsList = "/serviceproject/list";
|
||||||
static const String getServiceProjectDetail = "/serviceproject/details";
|
static const String getServiceProjectDetail = "/serviceproject/details";
|
||||||
@ -151,10 +150,14 @@ class ApiEndpoints {
|
|||||||
"/serviceproject/job/details";
|
"/serviceproject/job/details";
|
||||||
static const String editServiceProjectJob = "/serviceproject/job/edit";
|
static const String editServiceProjectJob = "/serviceproject/job/edit";
|
||||||
static const String createServiceProjectJob = "/serviceproject/job/create";
|
static const String createServiceProjectJob = "/serviceproject/job/create";
|
||||||
static const String serviceProjectUpateJobAttendance = "/serviceproject/job/attendance";
|
static const String serviceProjectUpateJobAttendance =
|
||||||
static const String serviceProjectUpateJobAttendanceLog = "/serviceproject/job/attendance/log";
|
"/serviceproject/job/attendance";
|
||||||
static const String getServiceProjectUpateJobAllocationList = "/serviceproject/get/allocation/list";
|
static const String serviceProjectUpateJobAttendanceLog =
|
||||||
static const String manageServiceProjectUpateJobAllocation = "/serviceproject/manage/allocation";
|
"/serviceproject/job/attendance/log";
|
||||||
|
static const String getServiceProjectUpateJobAllocationList =
|
||||||
|
"/serviceproject/get/allocation/list";
|
||||||
|
static const String manageServiceProjectUpateJobAllocation =
|
||||||
|
"/serviceproject/manage/allocation";
|
||||||
static const String getTeamRoles = "/master/team-roles/list";
|
static const String getTeamRoles = "/master/team-roles/list";
|
||||||
static const String getServiceProjectBranches = "/serviceproject/branch/list";
|
static const String getServiceProjectBranches = "/serviceproject/branch/list";
|
||||||
|
|
||||||
@ -168,5 +171,5 @@ class ApiEndpoints {
|
|||||||
static const String getInfraProjectsList = "/project/list";
|
static const String getInfraProjectsList = "/project/list";
|
||||||
static const String getInfraProjectDetail = "/project/details";
|
static const String getInfraProjectDetail = "/project/details";
|
||||||
static const String getInfraProjectTeamList = "/project/allocation";
|
static const String getInfraProjectTeamList = "/project/allocation";
|
||||||
|
static const String assignInfraProjectAllocation = "/project/allocation";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,6 +52,8 @@ import 'package:on_field_work/model/infra_project/infra_project_details.dart';
|
|||||||
import 'package:on_field_work/model/dashboard/collection_overview_model.dart';
|
import 'package:on_field_work/model/dashboard/collection_overview_model.dart';
|
||||||
import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart';
|
import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart';
|
||||||
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
||||||
|
import 'package:on_field_work/model/infra_project/assign_project_allocation_request.dart';
|
||||||
|
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
static const bool enableLogs = true;
|
static const bool enableLogs = true;
|
||||||
@ -2008,6 +2010,42 @@ class ApiService {
|
|||||||
label: "Comment Task", returnFullResponse: true);
|
label: "Comment Task", returnFullResponse: true);
|
||||||
return parsed != null && parsed['success'] == true;
|
return parsed != null && parsed['success'] == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<ProjectAllocationResponse?> assignEmployeesToProject({
|
||||||
|
required List<AssignProjectAllocationRequest> allocations,
|
||||||
|
}) async {
|
||||||
|
if (allocations.isEmpty) {
|
||||||
|
_log(
|
||||||
|
"No allocations provided for assignEmployeesToProject",
|
||||||
|
level: LogLevel.error,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final endpoint = ApiEndpoints.assignInfraProjectAllocation;
|
||||||
|
final payload = allocations.map((e) => e.toJson()).toList();
|
||||||
|
|
||||||
|
final response = await _safeApiCall(
|
||||||
|
endpoint,
|
||||||
|
method: 'POST',
|
||||||
|
body: payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response == null) return null;
|
||||||
|
|
||||||
|
final parsedJson = _parseAndDecryptResponse(
|
||||||
|
response,
|
||||||
|
label: "AssignInfraProjectAllocation",
|
||||||
|
returnFullResponse: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parsedJson == null || parsedJson is! Map<String, dynamic>) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProjectAllocationResponse.fromJson(parsedJson);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<ProjectAllocationResponse?> getInfraProjectTeamListApi({
|
static Future<ProjectAllocationResponse?> getInfraProjectTeamListApi({
|
||||||
required String projectId,
|
required String projectId,
|
||||||
String? serviceId,
|
String? serviceId,
|
||||||
|
|||||||
@ -38,11 +38,16 @@ Future<void> initializeApp() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 AUTH TOKEN HANDLER
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _handleAuthTokens() async {
|
Future<void> _handleAuthTokens() async {
|
||||||
final refreshToken = await LocalStorage.getRefreshToken();
|
final refreshToken = await LocalStorage.getRefreshToken();
|
||||||
|
|
||||||
if (refreshToken?.isNotEmpty ?? false) {
|
if (refreshToken?.isNotEmpty ?? false) {
|
||||||
logSafe("🔁 Refresh token found. Attempting to refresh JWT...");
|
logSafe("🔁 Refresh token found. Attempting to refresh JWT...");
|
||||||
final success = await AuthService.refreshToken();
|
final success = await AuthService.refreshToken();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logSafe("⚠️ Refresh token invalid or expired. User must login again.");
|
logSafe("⚠️ Refresh token invalid or expired. User must login again.");
|
||||||
}
|
}
|
||||||
@ -51,43 +56,67 @@ Future<void> _handleAuthTokens() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 UI SETUP
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupUI() async {
|
Future<void> _setupUI() async {
|
||||||
setPathUrlStrategy();
|
setPathUrlStrategy();
|
||||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
logSafe("💡 UI setup completed with default system behavior.");
|
logSafe("💡 UI setup completed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 FIREBASE + GEMINI SETUP
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupFirebase() async {
|
Future<void> _setupFirebase() async {
|
||||||
|
// Firebase Core
|
||||||
await Firebase.initializeApp();
|
await Firebase.initializeApp();
|
||||||
logSafe("💡 Firebase initialized.");
|
logSafe("🔥 Firebase initialized.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 LOCAL STORAGE SETUP
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupLocalStorage() async {
|
Future<void> _setupLocalStorage() async {
|
||||||
if (!LocalStorage.isInitialized) {
|
if (!LocalStorage.isInitialized) {
|
||||||
await LocalStorage.init();
|
await LocalStorage.init();
|
||||||
logSafe("💡 Local storage initialized.");
|
logSafe("💾 Local storage initialized.");
|
||||||
} else {
|
} else {
|
||||||
logSafe("ℹ️ Local storage already initialized, skipping.");
|
logSafe("ℹ️ Local storage already initialized. Skipping.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 DEVICE INFO
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupDeviceInfo() async {
|
Future<void> _setupDeviceInfo() async {
|
||||||
final deviceInfoService = DeviceInfoService();
|
final deviceInfoService = DeviceInfoService();
|
||||||
await deviceInfoService.init();
|
await deviceInfoService.init();
|
||||||
logSafe("📱 Device Info: ${deviceInfoService.deviceData}");
|
|
||||||
|
logSafe("📱 Device Info Loaded: ${deviceInfoService.deviceData}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 THEME SETUP
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupTheme() async {
|
Future<void> _setupTheme() async {
|
||||||
await ThemeCustomizer.init();
|
await ThemeCustomizer.init();
|
||||||
logSafe("💡 Theme customizer initialized.");
|
logSafe("🎨 Theme customizer initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 FIREBASE CLOUD MESSAGING (PUSH)
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
Future<void> _setupFirebaseMessaging() async {
|
Future<void> _setupFirebaseMessaging() async {
|
||||||
await FirebaseNotificationService().initialize();
|
await FirebaseNotificationService().initialize();
|
||||||
logSafe("💡 Firebase Messaging initialized.");
|
logSafe("📨 Firebase Messaging initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
|
/// 🔹 FINAL APP STYLE
|
||||||
|
/// ---------------------------------------------------------------------------
|
||||||
void _finalizeAppStyle() {
|
void _finalizeAppStyle() {
|
||||||
AppStyle.init();
|
AppStyle.init();
|
||||||
logSafe("💡 AppStyle initialized.");
|
logSafe("🎯 AppStyle initialized.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,9 +24,11 @@ class AssignTaskBottomSheet extends StatefulWidget {
|
|||||||
final String buildingName;
|
final String buildingName;
|
||||||
final String floorName;
|
final String floorName;
|
||||||
final String workAreaName;
|
final String workAreaName;
|
||||||
|
final String buildingId;
|
||||||
|
|
||||||
const AssignTaskBottomSheet({
|
const AssignTaskBottomSheet({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.buildingId,
|
||||||
required this.buildingName,
|
required this.buildingName,
|
||||||
required this.workLocation,
|
required this.workLocation,
|
||||||
required this.floorName,
|
required this.floorName,
|
||||||
@ -372,7 +374,7 @@ class _AssignTaskBottomSheetState extends State<AssignTaskBottomSheet> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAssignTaskPressed() {
|
Future<void> _onAssignTaskPressed() async {
|
||||||
final selectedTeam = controller.selectedEmployees;
|
final selectedTeam = controller.selectedEmployees;
|
||||||
|
|
||||||
if (selectedTeam.isEmpty) {
|
if (selectedTeam.isEmpty) {
|
||||||
@ -413,16 +415,20 @@ class _AssignTaskBottomSheetState extends State<AssignTaskBottomSheet> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.assignDailyTask(
|
final success = await controller.assignDailyTask(
|
||||||
workItemId: widget.workItemId,
|
workItemId: widget.workItemId,
|
||||||
plannedTask: target.toInt(),
|
plannedTask: target.toInt(),
|
||||||
description: description,
|
description: description,
|
||||||
taskTeam: selectedTeam.map((e) => e.id).toList(),
|
taskTeam: selectedTeam.map((e) => e.id).toList(),
|
||||||
assignmentDate: widget.assignmentDate,
|
assignmentDate: widget.assignmentDate,
|
||||||
buildingId: widget.buildingName,
|
buildingId: widget.buildingId,
|
||||||
projectId: selectedProjectId!,
|
projectId: selectedProjectId!,
|
||||||
organizationId: selectedOrganization?.id,
|
organizationId: selectedOrganization?.id,
|
||||||
serviceId: selectedService?.id,
|
serviceId: selectedService?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
class AssignProjectAllocationRequest {
|
||||||
|
final String employeeId;
|
||||||
|
final String projectId;
|
||||||
|
final String jobRoleId;
|
||||||
|
final String serviceId;
|
||||||
|
final bool status;
|
||||||
|
|
||||||
|
AssignProjectAllocationRequest({
|
||||||
|
required this.employeeId,
|
||||||
|
required this.projectId,
|
||||||
|
required this.jobRoleId,
|
||||||
|
required this.serviceId,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"employeeId": employeeId,
|
||||||
|
"projectId": projectId,
|
||||||
|
"jobRoleId": jobRoleId,
|
||||||
|
"serviceId": serviceId,
|
||||||
|
"status": status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -77,6 +77,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _sectionTitle(String title) {
|
Widget _sectionTitle(String title) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
||||||
@ -562,8 +563,12 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xfff5f6fa),
|
backgroundColor: const Color(0xfff5f6fa),
|
||||||
body: Layout(
|
body: Layout(
|
||||||
child: Padding(
|
child: Stack(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
children: [
|
||||||
|
// Main content
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -590,7 +595,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
_cardWrapper(
|
_cardWrapper(
|
||||||
child: MonthlyExpenseDashboardChart(),
|
child: MonthlyExpenseDashboardChart(),
|
||||||
),
|
),
|
||||||
MySpacing.height(20),
|
MySpacing.height(80), // give space under content
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -598,11 +603,12 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DashboardCardMeta {
|
class _DashboardCardMeta {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|||||||
@ -36,6 +36,10 @@ class _ManageReportingBottomSheetState
|
|||||||
final TextEditingController _primaryController = TextEditingController();
|
final TextEditingController _primaryController = TextEditingController();
|
||||||
final TextEditingController _secondaryController = TextEditingController();
|
final TextEditingController _secondaryController = TextEditingController();
|
||||||
|
|
||||||
|
final FocusNode _mainEmployeeFocus = FocusNode();
|
||||||
|
final FocusNode _primaryFocus = FocusNode();
|
||||||
|
final FocusNode _secondaryFocus = FocusNode();
|
||||||
|
|
||||||
final RxList<EmployeeModel> _filteredPrimary = <EmployeeModel>[].obs;
|
final RxList<EmployeeModel> _filteredPrimary = <EmployeeModel>[].obs;
|
||||||
final RxList<EmployeeModel> _filteredSecondary = <EmployeeModel>[].obs;
|
final RxList<EmployeeModel> _filteredSecondary = <EmployeeModel>[].obs;
|
||||||
final RxList<EmployeeModel> _selectedPrimary = <EmployeeModel>[].obs;
|
final RxList<EmployeeModel> _selectedPrimary = <EmployeeModel>[].obs;
|
||||||
@ -69,6 +73,10 @@ class _ManageReportingBottomSheetState
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_mainEmployeeFocus.dispose();
|
||||||
|
_primaryFocus.dispose();
|
||||||
|
_secondaryFocus.dispose();
|
||||||
|
|
||||||
_primaryController.dispose();
|
_primaryController.dispose();
|
||||||
_secondaryController.dispose();
|
_secondaryController.dispose();
|
||||||
_selectEmployeeController.dispose();
|
_selectEmployeeController.dispose();
|
||||||
@ -368,6 +376,7 @@ class _ManageReportingBottomSheetState
|
|||||||
_buildSearchSection(
|
_buildSearchSection(
|
||||||
label: "Primary Reporting Manager*",
|
label: "Primary Reporting Manager*",
|
||||||
controller: _primaryController,
|
controller: _primaryController,
|
||||||
|
focusNode: _primaryFocus,
|
||||||
filteredList: _filteredPrimary,
|
filteredList: _filteredPrimary,
|
||||||
selectedList: _selectedPrimary,
|
selectedList: _selectedPrimary,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
@ -379,6 +388,7 @@ class _ManageReportingBottomSheetState
|
|||||||
_buildSearchSection(
|
_buildSearchSection(
|
||||||
label: "Secondary Reporting Manager",
|
label: "Secondary Reporting Manager",
|
||||||
controller: _secondaryController,
|
controller: _secondaryController,
|
||||||
|
focusNode: _secondaryFocus,
|
||||||
filteredList: _filteredSecondary,
|
filteredList: _filteredSecondary,
|
||||||
selectedList: _selectedSecondary,
|
selectedList: _selectedSecondary,
|
||||||
isPrimary: false,
|
isPrimary: false,
|
||||||
@ -391,7 +401,9 @@ class _ManageReportingBottomSheetState
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).viewPadding.bottom + 20,
|
bottom: MediaQuery.of(context).viewPadding.bottom + 20,
|
||||||
left: 16, right: 16, top: 8,
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 8,
|
||||||
),
|
),
|
||||||
child: content,
|
child: content,
|
||||||
),
|
),
|
||||||
@ -449,6 +461,7 @@ class _ManageReportingBottomSheetState
|
|||||||
Widget _buildSearchSection({
|
Widget _buildSearchSection({
|
||||||
required String label,
|
required String label,
|
||||||
required TextEditingController controller,
|
required TextEditingController controller,
|
||||||
|
required FocusNode focusNode,
|
||||||
required RxList<EmployeeModel> filteredList,
|
required RxList<EmployeeModel> filteredList,
|
||||||
required RxList<EmployeeModel> selectedList,
|
required RxList<EmployeeModel> selectedList,
|
||||||
required bool isPrimary,
|
required bool isPrimary,
|
||||||
@ -459,20 +472,10 @@ class _ManageReportingBottomSheetState
|
|||||||
MyText.bodyMedium(label, fontWeight: 600),
|
MyText.bodyMedium(label, fontWeight: 600),
|
||||||
MySpacing.height(8),
|
MySpacing.height(8),
|
||||||
|
|
||||||
// Search field
|
_searchBar(
|
||||||
TextField(
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: InputDecoration(
|
focusNode: focusNode,
|
||||||
hintText: "Type to search employees...",
|
hint: "Type to search employees...",
|
||||||
isDense: true,
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey[50],
|
|
||||||
prefixIcon: const Icon(Icons.search, size: 18, color: Colors.grey),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Dropdown suggestions
|
// Dropdown suggestions
|
||||||
@ -567,19 +570,10 @@ class _ManageReportingBottomSheetState
|
|||||||
children: [
|
children: [
|
||||||
MyText.bodyMedium("Select Employee *", fontWeight: 600),
|
MyText.bodyMedium("Select Employee *", fontWeight: 600),
|
||||||
MySpacing.height(8),
|
MySpacing.height(8),
|
||||||
TextField(
|
_searchBar(
|
||||||
controller: _selectEmployeeController,
|
controller: _selectEmployeeController,
|
||||||
decoration: InputDecoration(
|
focusNode: _mainEmployeeFocus,
|
||||||
hintText: "Type to search employee...",
|
hint: "Type to search employee...",
|
||||||
isDense: true,
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey[50],
|
|
||||||
prefixIcon: const Icon(Icons.search, size: 18, color: Colors.grey),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (_filteredEmployees.isEmpty) return const SizedBox.shrink();
|
if (_filteredEmployees.isEmpty) return const SizedBox.shrink();
|
||||||
@ -641,4 +635,41 @@ class _ManageReportingBottomSheetState
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _searchBar({
|
||||||
|
required TextEditingController controller,
|
||||||
|
required FocusNode focusNode,
|
||||||
|
required String hint,
|
||||||
|
}) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => focusNode.requestFocus(),
|
||||||
|
child: Container(
|
||||||
|
height: 44,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.search, size: 18, color: Colors.grey),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hint,
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
299
lib/view/infraProject/assign_employee_infra_bottom_sheet.dart
Normal file
299
lib/view/infraProject/assign_employee_infra_bottom_sheet.dart
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import 'package:on_field_work/controller/tenant/organization_selection_controller.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/tenant/organization_selector.dart';
|
||||||
|
import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart';
|
||||||
|
import 'package:on_field_work/model/employees/employee_model.dart';
|
||||||
|
import 'package:on_field_work/model/employees/multiple_select_bottomsheet.dart';
|
||||||
|
import 'package:on_field_work/controller/tenant/service_controller.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/tenant/service_selector.dart';
|
||||||
|
import 'package:on_field_work/model/tenant/tenant_services_model.dart';
|
||||||
|
import 'package:on_field_work/helpers/services/api_service.dart';
|
||||||
|
import 'package:on_field_work/helpers/utils/base_bottom_sheet.dart';
|
||||||
|
import 'package:on_field_work/model/infra_project/assign_project_allocation_request.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class JobRole {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
JobRole({required this.id, required this.name});
|
||||||
|
|
||||||
|
factory JobRole.fromJson(Map<String, dynamic> json) {
|
||||||
|
return JobRole(
|
||||||
|
id: json['id'].toString(),
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignEmployeeBottomSheet extends StatefulWidget {
|
||||||
|
final String projectId;
|
||||||
|
|
||||||
|
const AssignEmployeeBottomSheet({
|
||||||
|
super.key,
|
||||||
|
required this.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AssignEmployeeBottomSheet> createState() =>
|
||||||
|
_AssignEmployeeBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AssignEmployeeBottomSheetState extends State<AssignEmployeeBottomSheet> {
|
||||||
|
late final OrganizationController _organizationController;
|
||||||
|
late final ServiceController _serviceController;
|
||||||
|
|
||||||
|
final RxList<EmployeeModel> _selectedEmployees = <EmployeeModel>[].obs;
|
||||||
|
|
||||||
|
Organization? _selectedOrganization;
|
||||||
|
JobRole? _selectedRole;
|
||||||
|
|
||||||
|
final RxBool _isLoadingRoles = false.obs;
|
||||||
|
final RxList<JobRole> _roles = <JobRole>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_organizationController = Get.put(
|
||||||
|
OrganizationController(),
|
||||||
|
tag: 'assign_employee_org',
|
||||||
|
);
|
||||||
|
|
||||||
|
_serviceController = Get.put(
|
||||||
|
ServiceController(),
|
||||||
|
tag: 'assign_employee_service',
|
||||||
|
);
|
||||||
|
|
||||||
|
_organizationController.fetchOrganizations(widget.projectId);
|
||||||
|
_serviceController.fetchServices(widget.projectId);
|
||||||
|
|
||||||
|
_fetchRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchRoles() async {
|
||||||
|
try {
|
||||||
|
_isLoadingRoles.value = true;
|
||||||
|
final res = await ApiService.getRoles();
|
||||||
|
if (res != null) {
|
||||||
|
_roles.assignAll(
|
||||||
|
res.map((e) => JobRole.fromJson(e)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_isLoadingRoles.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Get.delete<OrganizationController>(tag: 'assign_employee_org');
|
||||||
|
Get.delete<ServiceController>(tag: 'assign_employee_service');
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openEmployeeSelector() async {
|
||||||
|
final result = await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (_) => EmployeeSelectionBottomSheet(
|
||||||
|
title: 'Select Employee(s)',
|
||||||
|
multipleSelection: true,
|
||||||
|
initiallySelected: _selectedEmployees.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != null && result is List<EmployeeModel>) {
|
||||||
|
_selectedEmployees.assignAll(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleAssign() async {
|
||||||
|
if (_selectedEmployees.isEmpty ||
|
||||||
|
_selectedRole == null ||
|
||||||
|
_serviceController.selectedService == null) {
|
||||||
|
Get.snackbar('Error', 'Please complete all selections');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final allocations = _selectedEmployees
|
||||||
|
.map(
|
||||||
|
(e) => AssignProjectAllocationRequest(
|
||||||
|
employeeId: e.id,
|
||||||
|
projectId: widget.projectId,
|
||||||
|
jobRoleId: _selectedRole!.id,
|
||||||
|
serviceId: _serviceController.selectedService!.id,
|
||||||
|
status: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final res = await ApiService.assignEmployeesToProject(
|
||||||
|
allocations: allocations,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res?.success == true) {
|
||||||
|
Navigator.of(context).pop(true); // 🔥 triggers refresh
|
||||||
|
} else {
|
||||||
|
Get.snackbar('Error', res?.message ?? 'Assignment failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BaseBottomSheet(
|
||||||
|
title: 'Assign Employee',
|
||||||
|
submitText: 'Assign',
|
||||||
|
isSubmitting: false,
|
||||||
|
onCancel: () => Navigator.of(context).pop(),
|
||||||
|
onSubmit: _handleAssign,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
//ORGANIZATION
|
||||||
|
MyText.bodySmall(
|
||||||
|
'Organization',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
MySpacing.height(6),
|
||||||
|
|
||||||
|
OrganizationSelector(
|
||||||
|
controller: _organizationController,
|
||||||
|
height: 44,
|
||||||
|
onSelectionChanged: (Organization? org) async {
|
||||||
|
_selectedOrganization = org;
|
||||||
|
_selectedEmployees.clear();
|
||||||
|
_selectedRole = null;
|
||||||
|
_serviceController.clearSelection();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
MySpacing.height(20),
|
||||||
|
|
||||||
|
///EMPLOYEES (SEARCH)
|
||||||
|
MyText.bodySmall(
|
||||||
|
'Employees',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
MySpacing.height(6),
|
||||||
|
|
||||||
|
Obx(
|
||||||
|
() => InkWell(
|
||||||
|
onTap: _openEmployeeSelector,
|
||||||
|
child: _dropdownBox(
|
||||||
|
_selectedEmployees.isEmpty
|
||||||
|
? 'Select employee(s)'
|
||||||
|
: '${_selectedEmployees.length} employee(s) selected',
|
||||||
|
icon: Icons.search,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
MySpacing.height(20),
|
||||||
|
|
||||||
|
///SERVICE
|
||||||
|
MyText.bodySmall(
|
||||||
|
'Service',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
MySpacing.height(6),
|
||||||
|
|
||||||
|
ServiceSelector(
|
||||||
|
controller: _serviceController,
|
||||||
|
height: 44,
|
||||||
|
onSelectionChanged: (Service? service) async {
|
||||||
|
_selectedRole = null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
MySpacing.height(20),
|
||||||
|
|
||||||
|
/// JOB ROLE
|
||||||
|
MyText.bodySmall(
|
||||||
|
'Job Role',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
MySpacing.height(6),
|
||||||
|
|
||||||
|
Obx(() {
|
||||||
|
if (_isLoadingRoles.value) {
|
||||||
|
return _skeleton();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PopupMenuButton<JobRole>(
|
||||||
|
onSelected: (role) {
|
||||||
|
_selectedRole = role;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
itemBuilder: (context) {
|
||||||
|
if (_roles.isEmpty) {
|
||||||
|
return const [
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: false,
|
||||||
|
child: Text('No roles found'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return _roles
|
||||||
|
.map(
|
||||||
|
(r) => PopupMenuItem<JobRole>(
|
||||||
|
value: r,
|
||||||
|
child: Text(r.name),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
child: _dropdownBox(
|
||||||
|
_selectedRole?.name ?? 'Select role',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _dropdownBox(String text, {IconData icon = Icons.arrow_drop_down}) {
|
||||||
|
return Container(
|
||||||
|
height: 44,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(icon, color: Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _skeleton() {
|
||||||
|
return Container(
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,8 @@ import 'package:on_field_work/controller/infra_project/infra_project_screen_deta
|
|||||||
import 'package:on_field_work/view/taskPlanning/daily_progress_report.dart';
|
import 'package:on_field_work/view/taskPlanning/daily_progress_report.dart';
|
||||||
import 'package:on_field_work/view/taskPlanning/daily_task_planning.dart';
|
import 'package:on_field_work/view/taskPlanning/daily_task_planning.dart';
|
||||||
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
import 'package:on_field_work/model/infra_project/infra_team_list_model.dart';
|
||||||
|
import 'package:on_field_work/view/infraProject/assign_employee_infra_bottom_sheet.dart';
|
||||||
|
|
||||||
|
|
||||||
class InfraProjectDetailsScreen extends StatefulWidget {
|
class InfraProjectDetailsScreen extends StatefulWidget {
|
||||||
final String projectId;
|
final String projectId;
|
||||||
@ -83,6 +85,21 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
|||||||
_tabController = TabController(length: _tabs.length, vsync: this);
|
_tabController = TabController(length: _tabs.length, vsync: this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openAssignEmployeeBottomSheet() async {
|
||||||
|
final result = await showModalBottomSheet<bool>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (_) => AssignEmployeeBottomSheet(
|
||||||
|
projectId: widget.projectId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
controller.fetchProjectTeamList();
|
||||||
|
Get.snackbar('Success', 'Employee assigned successfully');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
@ -493,6 +510,19 @@ class _InfraProjectDetailsScreenState extends State<InfraProjectDetailsScreen>
|
|||||||
projectName: widget.projectName,
|
projectName: widget.projectName,
|
||||||
backgroundColor: appBarColor,
|
backgroundColor: appBarColor,
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
_openAssignEmployeeBottomSheet();
|
||||||
|
},
|
||||||
|
backgroundColor: contentTheme.primary,
|
||||||
|
icon: const Icon(Icons.person_add),
|
||||||
|
label: MyText(
|
||||||
|
'Assign Employee',
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: 500,
|
||||||
|
),
|
||||||
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|||||||
@ -472,6 +472,7 @@ class _DailyTaskPlanningScreenState extends State<DailyTaskPlanningScreen>
|
|||||||
),
|
),
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
AssignTaskBottomSheet(
|
AssignTaskBottomSheet(
|
||||||
|
buildingId: building.id,
|
||||||
buildingName:
|
buildingName:
|
||||||
building.name,
|
building.name,
|
||||||
floorName:
|
floorName:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user