feat: Implement pending expenses feature with API integration and UI widget
This commit is contained in:
parent
c78231d0fd
commit
f01608e4e7
@ -3,6 +3,7 @@ import 'package:marco/helpers/services/app_logger.dart';
|
|||||||
import 'package:marco/helpers/services/api_service.dart';
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
import 'package:marco/controller/project_controller.dart';
|
import 'package:marco/controller/project_controller.dart';
|
||||||
import 'package:marco/model/dashboard/project_progress_model.dart';
|
import 'package:marco/model/dashboard/project_progress_model.dart';
|
||||||
|
import 'package:marco/model/dashboard/pending_expenses_model.dart';
|
||||||
|
|
||||||
class DashboardController extends GetxController {
|
class DashboardController extends GetxController {
|
||||||
// =========================
|
// =========================
|
||||||
@ -48,7 +49,11 @@ class DashboardController extends GetxController {
|
|||||||
|
|
||||||
// Inject ProjectController
|
// Inject ProjectController
|
||||||
final ProjectController projectController = Get.find<ProjectController>();
|
final ProjectController projectController = Get.find<ProjectController>();
|
||||||
|
// Pending Expenses overview
|
||||||
|
// =========================
|
||||||
|
final RxBool isPendingExpensesLoading = false.obs;
|
||||||
|
final Rx<PendingExpensesData?> pendingExpensesData =
|
||||||
|
Rx<PendingExpensesData?>(null);
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@ -147,9 +152,35 @@ class DashboardController extends GetxController {
|
|||||||
fetchProjectProgress(),
|
fetchProjectProgress(),
|
||||||
fetchDashboardTasks(projectId: projectId),
|
fetchDashboardTasks(projectId: projectId),
|
||||||
fetchDashboardTeams(projectId: projectId),
|
fetchDashboardTeams(projectId: projectId),
|
||||||
|
fetchPendingExpenses(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> fetchPendingExpenses() async {
|
||||||
|
final String projectId = projectController.selectedProjectId.value;
|
||||||
|
if (projectId.isEmpty) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isPendingExpensesLoading.value = true;
|
||||||
|
final response =
|
||||||
|
await ApiService.getPendingExpensesApi(projectId: projectId);
|
||||||
|
|
||||||
|
if (response != null && response.success) {
|
||||||
|
pendingExpensesData.value = response.data;
|
||||||
|
logSafe('Pending expenses fetched successfully.', level: LogLevel.info);
|
||||||
|
} else {
|
||||||
|
pendingExpensesData.value = null;
|
||||||
|
logSafe('Failed to fetch pending expenses.', level: LogLevel.error);
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
pendingExpensesData.value = null;
|
||||||
|
logSafe('Error fetching pending expenses',
|
||||||
|
level: LogLevel.error, error: e, stackTrace: st);
|
||||||
|
} finally {
|
||||||
|
isPendingExpensesLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// API Calls
|
// API Calls
|
||||||
// =========================
|
// =========================
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
class ApiEndpoints {
|
class ApiEndpoints {
|
||||||
static const String baseUrl = "https://mapi.marcoaiot.com/api";
|
// static const String baseUrl = "https://mapi.marcoaiot.com/api";
|
||||||
|
static const String baseUrl = "https://ofwapi.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";
|
||||||
|
|
||||||
@ -10,12 +11,14 @@ class ApiEndpoints {
|
|||||||
static const String getDashboardTasks = "/dashboard/tasks";
|
static const String getDashboardTasks = "/dashboard/tasks";
|
||||||
static const String getDashboardTeams = "/dashboard/teams";
|
static const String getDashboardTeams = "/dashboard/teams";
|
||||||
static const String getDashboardProjects = "/dashboard/projects";
|
static const String getDashboardProjects = "/dashboard/projects";
|
||||||
|
static const String getDashboardMonthlyExpenses =
|
||||||
|
"/Dashboard/expense/monthly";
|
||||||
|
static const String getExpenseTypeReport = "/Dashboard/expense/type";
|
||||||
|
static const String getPendingExpenses = "/Dashboard/expense/pendings";
|
||||||
|
|
||||||
///// Projects Module API Endpoints
|
///// Projects Module API Endpoints
|
||||||
static const String createProject = "/project";
|
static const String createProject = "/project";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Attendance Module API Endpoints
|
// Attendance Module API Endpoints
|
||||||
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";
|
||||||
|
|||||||
@ -19,13 +19,14 @@ import 'package:marco/model/document/master_document_type_model.dart';
|
|||||||
import 'package:marco/model/document/document_details_model.dart';
|
import 'package:marco/model/document/document_details_model.dart';
|
||||||
import 'package:marco/model/document/document_version_model.dart';
|
import 'package:marco/model/document/document_version_model.dart';
|
||||||
import 'package:marco/model/attendance/organization_per_project_list_model.dart';
|
import 'package:marco/model/attendance/organization_per_project_list_model.dart';
|
||||||
|
import 'package:marco/model/dashboard/pending_expenses_model.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
static const bool enableLogs = true;
|
static const bool enableLogs = true;
|
||||||
static const Duration extendedTimeout = Duration(seconds: 60);
|
static const Duration extendedTimeout = Duration(seconds: 60);
|
||||||
|
|
||||||
static Future<String?> _getToken() async {
|
static Future<String?> _getToken() async {
|
||||||
final token = await LocalStorage.getJwtToken();
|
final token = LocalStorage.getJwtToken();
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
logSafe("No JWT token found. Logging out...");
|
logSafe("No JWT token found. Logging out...");
|
||||||
@ -38,7 +39,7 @@ class ApiService {
|
|||||||
logSafe("Access token is expired. Attempting refresh...");
|
logSafe("Access token is expired. Attempting refresh...");
|
||||||
final refreshed = await AuthService.refreshToken();
|
final refreshed = await AuthService.refreshToken();
|
||||||
if (refreshed) {
|
if (refreshed) {
|
||||||
return await LocalStorage.getJwtToken();
|
return LocalStorage.getJwtToken();
|
||||||
} else {
|
} else {
|
||||||
logSafe("Token refresh failed. Logging out immediately...");
|
logSafe("Token refresh failed. Logging out immediately...");
|
||||||
await LocalStorage.logout();
|
await LocalStorage.logout();
|
||||||
@ -55,7 +56,7 @@ class ApiService {
|
|||||||
"Access token is about to expire in ${difference.inSeconds}s. Refreshing...");
|
"Access token is about to expire in ${difference.inSeconds}s. Refreshing...");
|
||||||
final refreshed = await AuthService.refreshToken();
|
final refreshed = await AuthService.refreshToken();
|
||||||
if (refreshed) {
|
if (refreshed) {
|
||||||
return await LocalStorage.getJwtToken();
|
return LocalStorage.getJwtToken();
|
||||||
} else {
|
} else {
|
||||||
logSafe("Token refresh failed (near expiry). Logging out...");
|
logSafe("Token refresh failed (near expiry). Logging out...");
|
||||||
await LocalStorage.logout();
|
await LocalStorage.logout();
|
||||||
@ -288,6 +289,39 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get Pending Expenses
|
||||||
|
static Future<PendingExpensesResponse?> getPendingExpensesApi({
|
||||||
|
required String projectId,
|
||||||
|
}) async {
|
||||||
|
const endpoint = ApiEndpoints.getPendingExpenses;
|
||||||
|
logSafe("Fetching Pending Expenses for projectId: $projectId");
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _getRequest(
|
||||||
|
endpoint,
|
||||||
|
queryParams: {'projectId': projectId},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
logSafe("Pending Expenses request failed: null response",
|
||||||
|
level: LogLevel.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final jsonResponse =
|
||||||
|
_parseResponseForAllData(response, label: "Pending Expenses");
|
||||||
|
|
||||||
|
if (jsonResponse != null) {
|
||||||
|
return PendingExpensesResponse.fromJson(jsonResponse);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
logSafe("Exception during getPendingExpensesApi: $e",
|
||||||
|
level: LogLevel.error);
|
||||||
|
logSafe("StackTrace: $stack", level: LogLevel.debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Create Project API
|
/// Create Project API
|
||||||
static Future<bool> createProjectApi({
|
static Future<bool> createProjectApi({
|
||||||
|
|||||||
163
lib/helpers/widgets/dashbaord/expense_by_status_widget.dart
Normal file
163
lib/helpers/widgets/dashbaord/expense_by_status_widget.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:marco/controller/dashboard/dashboard_controller.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
|
||||||
|
class ExpenseByStatusWidget extends StatelessWidget {
|
||||||
|
final DashboardController controller;
|
||||||
|
|
||||||
|
const ExpenseByStatusWidget({super.key, required this.controller});
|
||||||
|
|
||||||
|
Widget _buildStatusTile({
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String title,
|
||||||
|
required String amount,
|
||||||
|
required String count,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
radius: 20,
|
||||||
|
child: Icon(icon, color: color, size: 22),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.titleMedium(
|
||||||
|
title,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
MyText.bodyMedium(
|
||||||
|
amount,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MyText.titleMedium(
|
||||||
|
count,
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
const Icon(Icons.chevron_right, color: Colors.blue, size: 22),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final data = controller.pendingExpensesData.value;
|
||||||
|
|
||||||
|
if (controller.isPendingExpensesLoading.value) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return Center(
|
||||||
|
child: MyText.bodyMedium("No expense status data available"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.titleLarge(
|
||||||
|
"Expense - By Status",
|
||||||
|
fontWeight: 700,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
MyText.bodyMedium(
|
||||||
|
controller.projectController.selectedProjectName.value,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Pending Payment
|
||||||
|
_buildStatusTile(
|
||||||
|
icon: Icons.currency_rupee,
|
||||||
|
color: Colors.blue,
|
||||||
|
title: "Pending Payment",
|
||||||
|
amount: "₹${data.processPending.amount.toStringAsFixed(1)}K",
|
||||||
|
count: data.processPending.count.toString(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Pending Approve
|
||||||
|
_buildStatusTile(
|
||||||
|
icon: Icons.check_circle_outline,
|
||||||
|
color: Colors.orange,
|
||||||
|
title: "Pending Approve",
|
||||||
|
amount: "₹${data.approvePending.amount.toStringAsFixed(1)}K",
|
||||||
|
count: data.approvePending.count.toString(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Pending Review
|
||||||
|
_buildStatusTile(
|
||||||
|
icon: Icons.search,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
title: "Pending Review",
|
||||||
|
amount: "₹${data.reviewPending.amount.toStringAsFixed(1)}K",
|
||||||
|
count: data.reviewPending.count.toString(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Draft
|
||||||
|
_buildStatusTile(
|
||||||
|
icon: Icons.insert_drive_file_outlined,
|
||||||
|
color: Colors.cyan,
|
||||||
|
title: "Draft",
|
||||||
|
amount: "₹${data.draft.amount.toStringAsFixed(1)}K",
|
||||||
|
count: data.draft.count.toString(),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Divider(color: Colors.grey.shade300),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.bodyMedium(
|
||||||
|
"Project Spendings:",
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
MyText.bodySmall(
|
||||||
|
"(All Processed Payments)",
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MyText.titleLarge(
|
||||||
|
"₹${(data.totalAmount / 1000).toStringAsFixed(2)}K >",
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: 700,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
74
lib/model/dashboard/expense_report_response_model.dart
Normal file
74
lib/model/dashboard/expense_report_response_model.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
class ExpenseReportResponse {
|
||||||
|
final bool success;
|
||||||
|
final String message;
|
||||||
|
final List<ExpenseReportData> data;
|
||||||
|
final dynamic errors;
|
||||||
|
final int statusCode;
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
ExpenseReportResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
this.errors,
|
||||||
|
required this.statusCode,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseReportResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseReportResponse(
|
||||||
|
success: json['success'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: json['data'] != null
|
||||||
|
? List<ExpenseReportData>.from(
|
||||||
|
json['data'].map((x) => ExpenseReportData.fromJson(x)))
|
||||||
|
: [],
|
||||||
|
errors: json['errors'],
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
timestamp: DateTime.parse(json['timestamp']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'success': success,
|
||||||
|
'message': message,
|
||||||
|
'data': data.map((x) => x.toJson()).toList(),
|
||||||
|
'errors': errors,
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseReportData {
|
||||||
|
final String monthName;
|
||||||
|
final int year;
|
||||||
|
final double total;
|
||||||
|
final int count;
|
||||||
|
|
||||||
|
ExpenseReportData({
|
||||||
|
required this.monthName,
|
||||||
|
required this.year,
|
||||||
|
required this.total,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseReportData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseReportData(
|
||||||
|
monthName: json['monthName'] ?? '',
|
||||||
|
year: json['year'] ?? 0,
|
||||||
|
total: json['total'] != null
|
||||||
|
? (json['total'] is int
|
||||||
|
? (json['total'] as int).toDouble()
|
||||||
|
: json['total'] as double)
|
||||||
|
: 0.0,
|
||||||
|
count: json['count'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'monthName': monthName,
|
||||||
|
'year': year,
|
||||||
|
'total': total,
|
||||||
|
'count': count,
|
||||||
|
};
|
||||||
|
}
|
||||||
105
lib/model/dashboard/expense_type_report_model.dart
Normal file
105
lib/model/dashboard/expense_type_report_model.dart
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
class ExpenseTypeReportResponse {
|
||||||
|
final bool success;
|
||||||
|
final String message;
|
||||||
|
final ExpenseTypeReportData data;
|
||||||
|
final dynamic errors;
|
||||||
|
final int statusCode;
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
ExpenseTypeReportResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
this.errors,
|
||||||
|
required this.statusCode,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseTypeReportResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseTypeReportResponse(
|
||||||
|
success: json['success'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: ExpenseTypeReportData.fromJson(json['data'] ?? {}),
|
||||||
|
errors: json['errors'],
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
timestamp: DateTime.parse(json['timestamp']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'success': success,
|
||||||
|
'message': message,
|
||||||
|
'data': data.toJson(),
|
||||||
|
'errors': errors,
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseTypeReportData {
|
||||||
|
final List<ExpenseTypeReportItem> report;
|
||||||
|
final double totalAmount;
|
||||||
|
|
||||||
|
ExpenseTypeReportData({
|
||||||
|
required this.report,
|
||||||
|
required this.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseTypeReportData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseTypeReportData(
|
||||||
|
report: json['report'] != null
|
||||||
|
? List<ExpenseTypeReportItem>.from(
|
||||||
|
json['report'].map((x) => ExpenseTypeReportItem.fromJson(x)))
|
||||||
|
: [],
|
||||||
|
totalAmount: json['totalAmount'] != null
|
||||||
|
? (json['totalAmount'] is int
|
||||||
|
? (json['totalAmount'] as int).toDouble()
|
||||||
|
: json['totalAmount'] as double)
|
||||||
|
: 0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'report': report.map((x) => x.toJson()).toList(),
|
||||||
|
'totalAmount': totalAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseTypeReportItem {
|
||||||
|
final String projectName;
|
||||||
|
final double totalApprovedAmount;
|
||||||
|
final double totalPendingAmount;
|
||||||
|
final double totalRejectedAmount;
|
||||||
|
final double totalProcessedAmount;
|
||||||
|
|
||||||
|
ExpenseTypeReportItem({
|
||||||
|
required this.projectName,
|
||||||
|
required this.totalApprovedAmount,
|
||||||
|
required this.totalPendingAmount,
|
||||||
|
required this.totalRejectedAmount,
|
||||||
|
required this.totalProcessedAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseTypeReportItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
double parseAmount(dynamic value) {
|
||||||
|
if (value == null) return 0.0;
|
||||||
|
return value is int ? value.toDouble() : value as double;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpenseTypeReportItem(
|
||||||
|
projectName: json['projectName'] ?? '',
|
||||||
|
totalApprovedAmount: parseAmount(json['totalApprovedAmount']),
|
||||||
|
totalPendingAmount: parseAmount(json['totalPendingAmount']),
|
||||||
|
totalRejectedAmount: parseAmount(json['totalRejectedAmount']),
|
||||||
|
totalProcessedAmount: parseAmount(json['totalProcessedAmount']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'projectName': projectName,
|
||||||
|
'totalApprovedAmount': totalApprovedAmount,
|
||||||
|
'totalPendingAmount': totalPendingAmount,
|
||||||
|
'totalRejectedAmount': totalRejectedAmount,
|
||||||
|
'totalProcessedAmount': totalProcessedAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
74
lib/model/dashboard/master_expense_types_model.dart
Normal file
74
lib/model/dashboard/master_expense_types_model.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
class ExpenseTypeResponse {
|
||||||
|
final bool success;
|
||||||
|
final String message;
|
||||||
|
final List<ExpenseTypeData> data;
|
||||||
|
final dynamic errors;
|
||||||
|
final int statusCode;
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
ExpenseTypeResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
this.errors,
|
||||||
|
required this.statusCode,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseTypeResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseTypeResponse(
|
||||||
|
success: json['success'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: json['data'] != null
|
||||||
|
? List<ExpenseTypeData>.from(
|
||||||
|
json['data'].map((x) => ExpenseTypeData.fromJson(x)))
|
||||||
|
: [],
|
||||||
|
errors: json['errors'],
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
timestamp: DateTime.parse(json['timestamp']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'success': success,
|
||||||
|
'message': message,
|
||||||
|
'data': data.map((x) => x.toJson()).toList(),
|
||||||
|
'errors': errors,
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseTypeData {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final bool noOfPersonsRequired;
|
||||||
|
final bool isAttachmentRequried;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
ExpenseTypeData({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.noOfPersonsRequired,
|
||||||
|
required this.isAttachmentRequried,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseTypeData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseTypeData(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
noOfPersonsRequired: json['noOfPersonsRequired'] ?? false,
|
||||||
|
isAttachmentRequried: json['isAttachmentRequried'] ?? false,
|
||||||
|
description: json['description'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'noOfPersonsRequired': noOfPersonsRequired,
|
||||||
|
'isAttachmentRequried': isAttachmentRequried,
|
||||||
|
'description': description,
|
||||||
|
};
|
||||||
|
}
|
||||||
169
lib/model/dashboard/pending_expenses_model.dart
Normal file
169
lib/model/dashboard/pending_expenses_model.dart
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class PendingExpensesResponse extends Equatable {
|
||||||
|
final bool success;
|
||||||
|
final String message;
|
||||||
|
final PendingExpensesData? data;
|
||||||
|
final dynamic errors;
|
||||||
|
final int statusCode;
|
||||||
|
final String timestamp;
|
||||||
|
|
||||||
|
const PendingExpensesResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
this.errors,
|
||||||
|
required this.statusCode,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PendingExpensesResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PendingExpensesResponse(
|
||||||
|
success: json['success'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: json['data'] != null
|
||||||
|
? PendingExpensesData.fromJson(json['data'])
|
||||||
|
: null,
|
||||||
|
errors: json['errors'],
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
timestamp: json['timestamp'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'success': success,
|
||||||
|
'message': message,
|
||||||
|
'data': data?.toJson(),
|
||||||
|
'errors': errors,
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingExpensesResponse copyWith({
|
||||||
|
bool? success,
|
||||||
|
String? message,
|
||||||
|
PendingExpensesData? data,
|
||||||
|
dynamic errors,
|
||||||
|
int? statusCode,
|
||||||
|
String? timestamp,
|
||||||
|
}) {
|
||||||
|
return PendingExpensesResponse(
|
||||||
|
success: success ?? this.success,
|
||||||
|
message: message ?? this.message,
|
||||||
|
data: data ?? this.data,
|
||||||
|
errors: errors ?? this.errors,
|
||||||
|
statusCode: statusCode ?? this.statusCode,
|
||||||
|
timestamp: timestamp ?? this.timestamp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [success, message, data, errors, statusCode, timestamp];
|
||||||
|
}
|
||||||
|
|
||||||
|
class PendingExpensesData extends Equatable {
|
||||||
|
final ExpenseStatus draft;
|
||||||
|
final ExpenseStatus reviewPending;
|
||||||
|
final ExpenseStatus approvePending;
|
||||||
|
final ExpenseStatus processPending;
|
||||||
|
final ExpenseStatus submited;
|
||||||
|
final double totalAmount;
|
||||||
|
|
||||||
|
const PendingExpensesData({
|
||||||
|
required this.draft,
|
||||||
|
required this.reviewPending,
|
||||||
|
required this.approvePending,
|
||||||
|
required this.processPending,
|
||||||
|
required this.submited,
|
||||||
|
required this.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PendingExpensesData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PendingExpensesData(
|
||||||
|
draft: ExpenseStatus.fromJson(json['draft']),
|
||||||
|
reviewPending: ExpenseStatus.fromJson(json['reviewPending']),
|
||||||
|
approvePending: ExpenseStatus.fromJson(json['approvePending']),
|
||||||
|
processPending: ExpenseStatus.fromJson(json['processPending']),
|
||||||
|
submited: ExpenseStatus.fromJson(json['submited']),
|
||||||
|
totalAmount: (json['totalAmount'] ?? 0).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'draft': draft.toJson(),
|
||||||
|
'reviewPending': reviewPending.toJson(),
|
||||||
|
'approvePending': approvePending.toJson(),
|
||||||
|
'processPending': processPending.toJson(),
|
||||||
|
'submited': submited.toJson(),
|
||||||
|
'totalAmount': totalAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingExpensesData copyWith({
|
||||||
|
ExpenseStatus? draft,
|
||||||
|
ExpenseStatus? reviewPending,
|
||||||
|
ExpenseStatus? approvePending,
|
||||||
|
ExpenseStatus? processPending,
|
||||||
|
ExpenseStatus? submited,
|
||||||
|
double? totalAmount,
|
||||||
|
}) {
|
||||||
|
return PendingExpensesData(
|
||||||
|
draft: draft ?? this.draft,
|
||||||
|
reviewPending: reviewPending ?? this.reviewPending,
|
||||||
|
approvePending: approvePending ?? this.approvePending,
|
||||||
|
processPending: processPending ?? this.processPending,
|
||||||
|
submited: submited ?? this.submited,
|
||||||
|
totalAmount: totalAmount ?? this.totalAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
draft,
|
||||||
|
reviewPending,
|
||||||
|
approvePending,
|
||||||
|
processPending,
|
||||||
|
submited,
|
||||||
|
totalAmount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseStatus extends Equatable {
|
||||||
|
final int count;
|
||||||
|
final double totalAmount;
|
||||||
|
|
||||||
|
const ExpenseStatus({
|
||||||
|
required this.count,
|
||||||
|
required this.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExpenseStatus.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExpenseStatus(
|
||||||
|
count: json['count'] ?? 0,
|
||||||
|
totalAmount: (json['totalAmount'] ?? 0).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'count': count,
|
||||||
|
'totalAmount': totalAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpenseStatus copyWith({
|
||||||
|
int? count,
|
||||||
|
double? totalAmount,
|
||||||
|
}) {
|
||||||
|
return ExpenseStatus(
|
||||||
|
count: count ?? this.count,
|
||||||
|
totalAmount: totalAmount ?? this.totalAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [count, totalAmount];
|
||||||
|
}
|
||||||
@ -80,6 +80,7 @@ dependencies:
|
|||||||
googleapis_auth: ^2.0.0
|
googleapis_auth: ^2.0.0
|
||||||
device_info_plus: ^11.3.0
|
device_info_plus: ^11.3.0
|
||||||
flutter_local_notifications: 19.4.0
|
flutter_local_notifications: 19.4.0
|
||||||
|
equatable: ^2.0.7
|
||||||
|
|
||||||
timeline_tile: ^2.0.0
|
timeline_tile: ^2.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user