feat: Update models and API service for improved data handling and type safety

This commit is contained in:
Vaibhav Surve 2025-06-22 18:40:10 +05:30
parent 6cdf35374d
commit 6c3370437d
11 changed files with 127 additions and 51 deletions

View File

@ -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)) {

View File

@ -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

View File

@ -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,
);
}

View File

@ -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?,

View File

@ -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,

View File

@ -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() ??

View 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,
};
}
}

View File

@ -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'],
);
}

View File

@ -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) ...[

View File

@ -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),

View File

@ -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,