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