feat: Update models and API service for improved data handling and type safety
This commit is contained in:
parent
6cdf35374d
commit
6c3370437d
@ -1,13 +1,13 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:marco/helpers/services/api_service.dart';
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
import 'package:marco/model/project_model.dart';
|
import 'package:marco/model/global_project_model.dart';
|
||||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
|
|
||||||
final Logger log = Logger();
|
final Logger log = Logger();
|
||||||
|
|
||||||
class ProjectController extends GetxController {
|
class ProjectController extends GetxController {
|
||||||
RxList<ProjectModel> projects = <ProjectModel>[].obs;
|
RxList<GlobalProjectModel> projects = <GlobalProjectModel>[].obs;
|
||||||
RxString? selectedProjectId;
|
RxString? selectedProjectId;
|
||||||
RxBool isProjectListExpanded = false.obs;
|
RxBool isProjectListExpanded = false.obs;
|
||||||
RxBool isProjectSelectionExpanded = false.obs;
|
RxBool isProjectSelectionExpanded = false.obs;
|
||||||
@ -16,7 +16,7 @@ class ProjectController extends GetxController {
|
|||||||
RxBool isLoading = true.obs;
|
RxBool isLoading = true.obs;
|
||||||
RxBool isLoadingProjects = true.obs;
|
RxBool isLoadingProjects = true.obs;
|
||||||
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
||||||
ProjectModel? get selectedProject {
|
GlobalProjectModel? get selectedProject {
|
||||||
if (selectedProjectId == null || selectedProjectId!.value.isEmpty)
|
if (selectedProjectId == null || selectedProjectId!.value.isEmpty)
|
||||||
return null;
|
return null;
|
||||||
return projects.firstWhereOrNull((p) => p.id == selectedProjectId!.value);
|
return projects.firstWhereOrNull((p) => p.id == selectedProjectId!.value);
|
||||||
@ -50,7 +50,7 @@ class ProjectController extends GetxController {
|
|||||||
|
|
||||||
if (response != null && response.isNotEmpty) {
|
if (response != null && response.isNotEmpty) {
|
||||||
projects.assignAll(
|
projects.assignAll(
|
||||||
response.map((json) => ProjectModel.fromJson(json)).toList());
|
response.map((json) => GlobalProjectModel.fromJson(json)).toList());
|
||||||
|
|
||||||
String? savedId = LocalStorage.getString('selectedProjectId');
|
String? savedId = LocalStorage.getString('selectedProjectId');
|
||||||
if (savedId != null && projects.any((p) => p.id == savedId)) {
|
if (savedId != null && projects.any((p) => p.id == savedId)) {
|
||||||
|
@ -46,21 +46,29 @@ class ApiService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamic _parseResponseForAllData(http.Response response,
|
static dynamic _parseResponseForAllData(http.Response response,
|
||||||
{String label = ''}) {
|
{String label = ''}) {
|
||||||
_log("$label Response: ${response.body}");
|
_log("$label Response: ${response.body}");
|
||||||
try {
|
|
||||||
final json = jsonDecode(response.body);
|
try {
|
||||||
if (response.statusCode == 200 && json['success'] == true) {
|
final body = response.body.trim();
|
||||||
return json;
|
if (body.isEmpty) throw FormatException("Empty response body");
|
||||||
}
|
|
||||||
_log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
|
final json = jsonDecode(body);
|
||||||
} catch (e) {
|
|
||||||
_log("Response parsing error [$label]: $e");
|
if (response.statusCode == 200 && json['success'] == true) {
|
||||||
|
return json;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
_log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
|
||||||
|
} catch (e) {
|
||||||
|
_log("Response parsing error [$label]: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static Future<http.Response?> _getRequest(
|
static Future<http.Response?> _getRequest(
|
||||||
String endpoint, {
|
String endpoint, {
|
||||||
Map<String, String>? queryParams,
|
Map<String, String>? queryParams,
|
||||||
@ -144,7 +152,7 @@ class ApiService {
|
|||||||
(res) => res != null ? _parseResponse(res, label: 'Projects') : null);
|
(res) => res != null ? _parseResponse(res, label: 'Projects') : null);
|
||||||
|
|
||||||
static Future<List<dynamic>?> getGlobalProjects() async =>
|
static Future<List<dynamic>?> getGlobalProjects() async =>
|
||||||
_getRequest(ApiEndpoints.getProjects).then((res) =>
|
_getRequest(ApiEndpoints.getGlobalProjects).then((res) =>
|
||||||
res != null ? _parseResponse(res, label: 'Global Projects') : null);
|
res != null ? _parseResponse(res, label: 'Global Projects') : null);
|
||||||
|
|
||||||
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async =>
|
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async =>
|
||||||
@ -403,10 +411,19 @@ class ApiService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>?> getWorkStatus() async =>
|
static Future<Map<String, dynamic>?> getWorkStatus() async {
|
||||||
_getRequest(ApiEndpoints.getWorkStatus).then((res) => res != null
|
final res = await _getRequest(ApiEndpoints.getWorkStatus);
|
||||||
? _parseResponseForAllData(res, label: 'Work Status')
|
if (res == null) {
|
||||||
: null);
|
_log('Work Status API returned null');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log('Work Status raw response: ${res.body}');
|
||||||
|
|
||||||
|
return _parseResponseForAllData(res, label: 'Work Status')
|
||||||
|
as Map<String, dynamic>?;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>?> getMasterWorkCategories() async =>
|
static Future<Map<String, dynamic>?> getMasterWorkCategories() async =>
|
||||||
_getRequest(ApiEndpoints.getmasterWorkCategories).then((res) =>
|
_getRequest(ApiEndpoints.getmasterWorkCategories).then((res) =>
|
||||||
res != null
|
res != null
|
||||||
|
@ -6,8 +6,8 @@ class AttendanceModel {
|
|||||||
final DateTime startDate;
|
final DateTime startDate;
|
||||||
final DateTime endDate;
|
final DateTime endDate;
|
||||||
final int teamSize;
|
final int teamSize;
|
||||||
final int completedWork;
|
final double completedWork;
|
||||||
final int plannedWork;
|
final double plannedWork;
|
||||||
|
|
||||||
AttendanceModel({
|
AttendanceModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -30,8 +30,8 @@ class AttendanceModel {
|
|||||||
startDate: DateTime.tryParse(json['startDate']?.toString() ?? '') ?? DateTime.now(),
|
startDate: DateTime.tryParse(json['startDate']?.toString() ?? '') ?? DateTime.now(),
|
||||||
endDate: DateTime.tryParse(json['endDate']?.toString() ?? '') ?? DateTime.now(),
|
endDate: DateTime.tryParse(json['endDate']?.toString() ?? '') ?? DateTime.now(),
|
||||||
teamSize: int.tryParse(json['teamSize']?.toString() ?? '') ?? 0,
|
teamSize: int.tryParse(json['teamSize']?.toString() ?? '') ?? 0,
|
||||||
completedWork: int.tryParse(json['completedWork']?.toString() ?? '') ?? 0,
|
completedWork: double.tryParse(json['completedWork']?.toString() ?? '') ?? 0,
|
||||||
plannedWork: int.tryParse(json['plannedWork']?.toString() ?? '') ?? 0,
|
plannedWork: double.tryParse(json['plannedWork']?.toString() ?? '') ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +127,8 @@ class WorkItem {
|
|||||||
final WorkAreaBasic? workArea;
|
final WorkAreaBasic? workArea;
|
||||||
final ActivityMaster? activityMaster;
|
final ActivityMaster? activityMaster;
|
||||||
final WorkCategoryMaster? workCategoryMaster;
|
final WorkCategoryMaster? workCategoryMaster;
|
||||||
final int? plannedWork;
|
final double? plannedWork;
|
||||||
final int? completedWork;
|
final double? completedWork;
|
||||||
final DateTime? taskDate;
|
final DateTime? taskDate;
|
||||||
final String? tenantId;
|
final String? tenantId;
|
||||||
final Tenant? tenant;
|
final Tenant? tenant;
|
||||||
@ -165,8 +165,12 @@ class WorkItem {
|
|||||||
? WorkCategoryMaster.fromJson(
|
? WorkCategoryMaster.fromJson(
|
||||||
json['workCategoryMaster'] as Map<String, dynamic>)
|
json['workCategoryMaster'] as Map<String, dynamic>)
|
||||||
: null,
|
: null,
|
||||||
plannedWork: json['plannedWork'] as int?,
|
plannedWork: json['plannedWork'] != null
|
||||||
completedWork: json['completedWork'] as int?,
|
? (json['plannedWork'] as num).toDouble()
|
||||||
|
: null,
|
||||||
|
completedWork: json['completedWork'] != null
|
||||||
|
? (json['completedWork'] as num).toDouble()
|
||||||
|
: null,
|
||||||
taskDate:
|
taskDate:
|
||||||
json['taskDate'] != null ? DateTime.tryParse(json['taskDate']) : null,
|
json['taskDate'] != null ? DateTime.tryParse(json['taskDate']) : null,
|
||||||
tenantId: json['tenantId'] as String?,
|
tenantId: json['tenantId'] as String?,
|
||||||
|
@ -82,8 +82,8 @@ class TaskActionButtons {
|
|||||||
textStyle: const TextStyle(fontSize: 14),
|
textStyle: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final taskData =
|
final taskData = _prepareTaskData(task: task, completed: task.completedTask.toInt());
|
||||||
_prepareTaskData(task: task, completed: task.completedTask);
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
@ -4,8 +4,8 @@ class TaskModel {
|
|||||||
final String id;
|
final String id;
|
||||||
final WorkItem? workItem;
|
final WorkItem? workItem;
|
||||||
final String workItemId;
|
final String workItemId;
|
||||||
final int plannedTask;
|
final double plannedTask;
|
||||||
final int completedTask;
|
final double completedTask;
|
||||||
final AssignedBy assignedBy;
|
final AssignedBy assignedBy;
|
||||||
final AssignedBy? approvedBy;
|
final AssignedBy? approvedBy;
|
||||||
final List<TeamMember> teamMembers;
|
final List<TeamMember> teamMembers;
|
||||||
@ -37,8 +37,8 @@ class TaskModel {
|
|||||||
workItem:
|
workItem:
|
||||||
json['workItem'] != null ? WorkItem.fromJson(json['workItem']) : null,
|
json['workItem'] != null ? WorkItem.fromJson(json['workItem']) : null,
|
||||||
workItemId: json['workItemId'],
|
workItemId: json['workItemId'],
|
||||||
plannedTask: json['plannedTask'],
|
plannedTask: (json['plannedTask'] as num).toDouble(),
|
||||||
completedTask: json['completedTask'],
|
completedTask: (json['completedTask'] as num).toDouble(),
|
||||||
assignedBy: AssignedBy.fromJson(json['assignedBy']),
|
assignedBy: AssignedBy.fromJson(json['assignedBy']),
|
||||||
approvedBy: json['approvedBy'] != null
|
approvedBy: json['approvedBy'] != null
|
||||||
? AssignedBy.fromJson(json['approvedBy'])
|
? AssignedBy.fromJson(json['approvedBy'])
|
||||||
@ -60,8 +60,8 @@ class WorkItem {
|
|||||||
final String? id;
|
final String? id;
|
||||||
final ActivityMaster? activityMaster;
|
final ActivityMaster? activityMaster;
|
||||||
final WorkArea? workArea;
|
final WorkArea? workArea;
|
||||||
final int? plannedWork;
|
final double? plannedWork;
|
||||||
final int? completedWork;
|
final double? completedWork;
|
||||||
final List<String> preSignedUrls;
|
final List<String> preSignedUrls;
|
||||||
|
|
||||||
WorkItem({
|
WorkItem({
|
||||||
@ -81,8 +81,8 @@ class WorkItem {
|
|||||||
: null,
|
: null,
|
||||||
workArea:
|
workArea:
|
||||||
json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null,
|
json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null,
|
||||||
plannedWork: json['plannedWork'],
|
plannedWork: (json['plannedWork'] as num?)?.toDouble(),
|
||||||
completedWork: json['completedWork'],
|
completedWork: (json['completedWork'] as num?)?.toDouble(),
|
||||||
preSignedUrls: (json['preSignedUrls'] as List<dynamic>?)
|
preSignedUrls: (json['preSignedUrls'] as List<dynamic>?)
|
||||||
?.map((e) => e.toString())
|
?.map((e) => e.toString())
|
||||||
.toList() ??
|
.toList() ??
|
||||||
|
51
lib/model/global_project_model.dart
Normal file
51
lib/model/global_project_model.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
class GlobalProjectModel {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String projectAddress;
|
||||||
|
final String contactPerson;
|
||||||
|
final DateTime startDate;
|
||||||
|
final DateTime endDate;
|
||||||
|
final int teamSize;
|
||||||
|
final String projectStatusId;
|
||||||
|
final String? tenantId;
|
||||||
|
|
||||||
|
GlobalProjectModel({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.projectAddress,
|
||||||
|
required this.contactPerson,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate,
|
||||||
|
required this.teamSize,
|
||||||
|
required this.projectStatusId,
|
||||||
|
this.tenantId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory GlobalProjectModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return GlobalProjectModel(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
projectAddress: json['projectAddress'] ?? '',
|
||||||
|
contactPerson: json['contactPerson'] ?? '',
|
||||||
|
startDate: DateTime.parse(json['startDate']),
|
||||||
|
endDate: DateTime.parse(json['endDate']),
|
||||||
|
teamSize: json['teamSize'] ?? 0, // ✅ SAFER
|
||||||
|
projectStatusId: json['projectStatusId'] ?? '',
|
||||||
|
tenantId: json['tenantId'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'projectAddress': projectAddress,
|
||||||
|
'contactPerson': contactPerson,
|
||||||
|
'startDate': startDate.toIso8601String(),
|
||||||
|
'endDate': endDate.toIso8601String(),
|
||||||
|
'teamSize': teamSize,
|
||||||
|
'projectStatusId': projectStatusId,
|
||||||
|
'tenantId': tenantId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ class ProjectModel {
|
|||||||
final DateTime startDate;
|
final DateTime startDate;
|
||||||
final DateTime endDate;
|
final DateTime endDate;
|
||||||
final int teamSize;
|
final int teamSize;
|
||||||
final int completedWork;
|
final double completedWork;
|
||||||
final int plannedWork;
|
final double plannedWork;
|
||||||
final String projectStatusId;
|
final String projectStatusId;
|
||||||
final String? tenantId;
|
final String? tenantId;
|
||||||
|
|
||||||
@ -35,8 +35,12 @@ class ProjectModel {
|
|||||||
startDate: DateTime.parse(json['startDate']),
|
startDate: DateTime.parse(json['startDate']),
|
||||||
endDate: DateTime.parse(json['endDate']),
|
endDate: DateTime.parse(json['endDate']),
|
||||||
teamSize: json['teamSize'],
|
teamSize: json['teamSize'],
|
||||||
completedWork: json['completedWork'],
|
completedWork: json['completedWork'] != null
|
||||||
plannedWork: json['plannedWork'],
|
? (json['completedWork'] as num).toDouble()
|
||||||
|
: 0.0,
|
||||||
|
plannedWork: json['plannedWork'] != null
|
||||||
|
? (json['plannedWork'] as num).toDouble()
|
||||||
|
: 0.0,
|
||||||
projectStatusId: json['projectStatusId'],
|
projectStatusId: json['projectStatusId'],
|
||||||
tenantId: json['tenantId'],
|
tenantId: json['tenantId'],
|
||||||
);
|
);
|
||||||
|
@ -59,7 +59,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
_buildDashboardStats(),
|
_buildDashboardStats(),
|
||||||
MySpacing.height(24),
|
MySpacing.height(24),
|
||||||
AttendanceDashboardChart(),
|
// AttendanceDashboardChart(),
|
||||||
|
|
||||||
MySpacing.height(300),
|
MySpacing.height(300),
|
||||||
if (!hasMpin) ...[
|
if (!hasMpin) ...[
|
||||||
|
@ -467,7 +467,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
|||||||
TaskActionButtons.reportButton(
|
TaskActionButtons.reportButton(
|
||||||
context: context,
|
context: context,
|
||||||
task: task,
|
task: task,
|
||||||
completed: completed,
|
completed: completed.toInt(),
|
||||||
refreshCallback: _refreshData,
|
refreshCallback: _refreshData,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@ -478,7 +478,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
|||||||
parentTaskID: parentTaskID,
|
parentTaskID: parentTaskID,
|
||||||
workAreaId: workAreaId.toString(),
|
workAreaId: workAreaId.toString(),
|
||||||
activityId: activityId.toString(),
|
activityId: activityId.toString(),
|
||||||
completed: completed,
|
completed: completed.toInt(),
|
||||||
refreshCallback: _refreshData,
|
refreshCallback: _refreshData,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
@ -280,10 +280,10 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
|
|||||||
floorExpansionState[floorWorkAreaKey] ?? false;
|
floorExpansionState[floorWorkAreaKey] ?? false;
|
||||||
final totalPlanned = area.workItems
|
final totalPlanned = area.workItems
|
||||||
.map((wi) => wi.workItem.plannedWork ?? 0)
|
.map((wi) => wi.workItem.plannedWork ?? 0)
|
||||||
.fold<int>(0, (prev, curr) => prev + curr);
|
.fold<double>(0, (prev, curr) => prev + curr);
|
||||||
final totalCompleted = area.workItems
|
final totalCompleted = area.workItems
|
||||||
.map((wi) => wi.workItem.completedWork ?? 0)
|
.map((wi) => wi.workItem.completedWork ?? 0)
|
||||||
.fold<int>(0, (prev, curr) => prev + curr);
|
.fold<double>(0, (prev, curr) => prev + curr);
|
||||||
final totalProgress = totalPlanned == 0
|
final totalProgress = totalPlanned == 0
|
||||||
? 0.0
|
? 0.0
|
||||||
: (totalCompleted / totalPlanned).clamp(0.0, 1.0);
|
: (totalCompleted / totalPlanned).clamp(0.0, 1.0);
|
||||||
@ -430,7 +430,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
final pendingTask =
|
final pendingTask =
|
||||||
(planned - completed)
|
(planned - completed)
|
||||||
.clamp(0, planned);
|
.clamp(0, planned).toInt();
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user