fixed issues
This commit is contained in:
parent
fbfc54159c
commit
1bf676f64a
@ -16,7 +16,7 @@ class DashboardController extends GetxController {
|
|||||||
final ProjectController projectController = Get.put(ProjectController());
|
final ProjectController projectController = Get.put(ProjectController());
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 1. STATE VARIABLES
|
// 1. STATE VARIABLES (No functional change)
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
// Attendance
|
// Attendance
|
||||||
@ -50,6 +50,7 @@ class DashboardController extends GetxController {
|
|||||||
|
|
||||||
final isExpenseTypeReportLoading = false.obs;
|
final isExpenseTypeReportLoading = false.obs;
|
||||||
final expenseTypeReportData = Rx<ExpenseTypeReportData?>(null);
|
final expenseTypeReportData = Rx<ExpenseTypeReportData?>(null);
|
||||||
|
// OPTIMIZED: Use const Duration for better performance
|
||||||
final expenseReportStartDate =
|
final expenseReportStartDate =
|
||||||
DateTime.now().subtract(const Duration(days: 15)).obs;
|
DateTime.now().subtract(const Duration(days: 15)).obs;
|
||||||
final expenseReportEndDate = DateTime.now().obs;
|
final expenseReportEndDate = DateTime.now().obs;
|
||||||
@ -77,28 +78,32 @@ class DashboardController extends GetxController {
|
|||||||
final isPurchaseInvoiceLoading = true.obs;
|
final isPurchaseInvoiceLoading = true.obs;
|
||||||
final purchaseInvoiceOverviewData = Rx<PurchaseInvoiceOverviewData?>(null);
|
final purchaseInvoiceOverviewData = Rx<PurchaseInvoiceOverviewData?>(null);
|
||||||
// Constants
|
// Constants
|
||||||
final List<String> ranges = ['7D', '15D', '30D'];
|
final List<String> ranges = const [
|
||||||
|
'7D',
|
||||||
|
'15D',
|
||||||
|
'30D'
|
||||||
|
]; // OPTIMIZED: Added const
|
||||||
static const _rangeDaysMap = {
|
static const _rangeDaysMap = {
|
||||||
|
// OPTIMIZED: Added const
|
||||||
'7D': 7,
|
'7D': 7,
|
||||||
'15D': 15,
|
'15D': 15,
|
||||||
'30D': 30,
|
'30D': 30,
|
||||||
'3M': 90,
|
'3M': 90,
|
||||||
'6M': 180
|
'6M': 180
|
||||||
};
|
};
|
||||||
|
// DSO Calculation Constants (OPTIMIZED: Added const)
|
||||||
// =========================
|
|
||||||
// 2. COMPUTED PROPERTIES
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
int getAttendanceDays() => _rangeDaysMap[attendanceSelectedRange.value] ?? 7;
|
|
||||||
int getProjectDays() => _rangeDaysMap[projectSelectedRange.value] ?? 7;
|
|
||||||
|
|
||||||
// DSO Calculation Constants
|
|
||||||
static const double _w0_30 = 15.0;
|
static const double _w0_30 = 15.0;
|
||||||
static const double _w30_60 = 45.0;
|
static const double _w30_60 = 45.0;
|
||||||
static const double _w60_90 = 75.0;
|
static const double _w60_90 = 75.0;
|
||||||
static const double _w90_plus = 105.0;
|
static const double _w90_plus = 105.0;
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// 2. COMPUTED PROPERTIES (No functional change)
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
int getAttendanceDays() => _rangeDaysMap[attendanceSelectedRange.value] ?? 7;
|
||||||
|
int getProjectDays() => _rangeDaysMap[projectSelectedRange.value] ?? 7;
|
||||||
|
|
||||||
double get calculatedDSO {
|
double get calculatedDSO {
|
||||||
final data = collectionOverviewData.value;
|
final data = collectionOverviewData.value;
|
||||||
if (data == null || data.totalDueAmount == 0) return 0.0;
|
if (data == null || data.totalDueAmount == 0) return 0.0;
|
||||||
@ -112,7 +117,7 @@ class DashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 3. LIFECYCLE
|
// 3. LIFECYCLE (No functional change)
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -129,6 +134,7 @@ class DashboardController extends GetxController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Expense Report Date Listener
|
// Expense Report Date Listener
|
||||||
|
// OPTIMIZED: Using `everAll` is already efficient for this logic
|
||||||
everAll([expenseReportStartDate, expenseReportEndDate], (_) {
|
everAll([expenseReportStartDate, expenseReportEndDate], (_) {
|
||||||
if (projectController.selectedProjectId.value.isNotEmpty) {
|
if (projectController.selectedProjectId.value.isNotEmpty) {
|
||||||
fetchExpenseTypeReport(
|
fetchExpenseTypeReport(
|
||||||
@ -144,7 +150,7 @@ class DashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// 4. USER ACTIONS
|
// 4. USER ACTIONS (No functional change)
|
||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
void updateAttendanceRange(String range) =>
|
void updateAttendanceRange(String range) =>
|
||||||
@ -163,7 +169,7 @@ class DashboardController extends GetxController {
|
|||||||
void updateMonthlyExpenseDuration(MonthlyExpenseDuration duration) {
|
void updateMonthlyExpenseDuration(MonthlyExpenseDuration duration) {
|
||||||
selectedMonthlyExpenseDuration.value = duration;
|
selectedMonthlyExpenseDuration.value = duration;
|
||||||
|
|
||||||
// Efficient Map lookup instead of Switch
|
// OPTIMIZED: The map approach is highly efficient.
|
||||||
const durationMap = {
|
const durationMap = {
|
||||||
MonthlyExpenseDuration.oneMonth: 1,
|
MonthlyExpenseDuration.oneMonth: 1,
|
||||||
MonthlyExpenseDuration.threeMonths: 3,
|
MonthlyExpenseDuration.threeMonths: 3,
|
||||||
@ -189,13 +195,17 @@ class DashboardController extends GetxController {
|
|||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/// Wrapper to reduce try-finally boilerplate for loading states
|
/// Wrapper to reduce try-finally boilerplate for loading states
|
||||||
|
// OPTIMIZED: Renamed variable to avoid shadowing standard library.
|
||||||
Future<void> _executeApiCall(
|
Future<void> _executeApiCall(
|
||||||
RxBool loader, Future<void> Function() apiLogic) async {
|
RxBool loaderRx, Future<void> Function() apiLogic) async {
|
||||||
loader.value = true;
|
loaderRx.value = true;
|
||||||
try {
|
try {
|
||||||
await apiLogic();
|
await apiLogic();
|
||||||
|
} catch (e, stack) {
|
||||||
|
// OPTIMIZED: Added logging of error for better debugging
|
||||||
|
logSafe('API Call Failed: $e', level: LogLevel.error, stackTrace: stack);
|
||||||
} finally {
|
} finally {
|
||||||
loader.value = false;
|
loaderRx.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +213,7 @@ class DashboardController extends GetxController {
|
|||||||
final String projectId = projectController.selectedProjectId.value;
|
final String projectId = projectController.selectedProjectId.value;
|
||||||
if (projectId.isEmpty) return;
|
if (projectId.isEmpty) return;
|
||||||
|
|
||||||
|
// OPTIMIZED: Ensure MasterData is fetched only once if possible, but kept in Future.wait for robustness
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
fetchRoleWiseAttendance(),
|
fetchRoleWiseAttendance(),
|
||||||
fetchProjectProgress(),
|
fetchProjectProgress(),
|
||||||
@ -227,6 +238,7 @@ class DashboardController extends GetxController {
|
|||||||
await _executeApiCall(isCollectionOverviewLoading, () async {
|
await _executeApiCall(isCollectionOverviewLoading, () async {
|
||||||
final response =
|
final response =
|
||||||
await ApiService.getCollectionOverview(projectId: projectId);
|
await ApiService.getCollectionOverview(projectId: projectId);
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
collectionOverviewData.value =
|
collectionOverviewData.value =
|
||||||
(response?.success == true) ? response!.data : null;
|
(response?.success == true) ? response!.data : null;
|
||||||
});
|
});
|
||||||
@ -237,21 +249,26 @@ class DashboardController extends GetxController {
|
|||||||
final response = await ApiService.getAttendanceForDashboard(projectId);
|
final response = await ApiService.getAttendanceForDashboard(projectId);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
employees.value = response;
|
employees.value = response;
|
||||||
|
// OPTIMIZED: Use `putIfAbsent` and ensure the map holds an RxBool
|
||||||
for (var emp in employees) {
|
for (var emp in employees) {
|
||||||
uploadingStates.putIfAbsent(emp.id, () => false.obs);
|
uploadingStates.putIfAbsent(emp.id, () => false.obs);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
employees.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchMasterData() async {
|
Future<void> fetchMasterData() async {
|
||||||
try {
|
// OPTIMIZATION: Use _executeApiCall for consistency
|
||||||
|
await _executeApiCall(false.obs, () async {
|
||||||
|
// Use a local RxBool since there's no dedicated loader state
|
||||||
final data = await ApiService.getMasterExpenseTypes();
|
final data = await ApiService.getMasterExpenseTypes();
|
||||||
if (data is List) {
|
if (data is List) {
|
||||||
expenseTypes.value =
|
expenseTypes.value =
|
||||||
data.map((e) => ExpenseTypeModel.fromJson(e)).toList();
|
data.map((e) => ExpenseTypeModel.fromJson(e)).toList();
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchMonthlyExpenses({String? categoryId}) async {
|
Future<void> fetchMonthlyExpenses({String? categoryId}) async {
|
||||||
@ -260,6 +277,7 @@ class DashboardController extends GetxController {
|
|||||||
categoryId: categoryId,
|
categoryId: categoryId,
|
||||||
months: selectedMonthsCount.value,
|
months: selectedMonthsCount.value,
|
||||||
);
|
);
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
monthlyExpenseList.value =
|
monthlyExpenseList.value =
|
||||||
(response?.success == true) ? response!.data : [];
|
(response?.success == true) ? response!.data : [];
|
||||||
});
|
});
|
||||||
@ -273,6 +291,7 @@ class DashboardController extends GetxController {
|
|||||||
final response = await ApiService.getPurchaseInvoiceOverview(
|
final response = await ApiService.getPurchaseInvoiceOverview(
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
);
|
);
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
purchaseInvoiceOverviewData.value =
|
purchaseInvoiceOverviewData.value =
|
||||||
(response?.success == true) ? response!.data : null;
|
(response?.success == true) ? response!.data : null;
|
||||||
});
|
});
|
||||||
@ -284,6 +303,7 @@ class DashboardController extends GetxController {
|
|||||||
|
|
||||||
await _executeApiCall(isPendingExpensesLoading, () async {
|
await _executeApiCall(isPendingExpensesLoading, () async {
|
||||||
final response = await ApiService.getPendingExpensesApi(projectId: id);
|
final response = await ApiService.getPendingExpensesApi(projectId: id);
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
pendingExpensesData.value =
|
pendingExpensesData.value =
|
||||||
(response?.success == true) ? response!.data : null;
|
(response?.success == true) ? response!.data : null;
|
||||||
});
|
});
|
||||||
@ -296,6 +316,7 @@ class DashboardController extends GetxController {
|
|||||||
await _executeApiCall(isAttendanceLoading, () async {
|
await _executeApiCall(isAttendanceLoading, () async {
|
||||||
final response = await ApiService.getDashboardAttendanceOverview(
|
final response = await ApiService.getDashboardAttendanceOverview(
|
||||||
id, getAttendanceDays());
|
id, getAttendanceDays());
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
roleWiseData.value =
|
roleWiseData.value =
|
||||||
response?.map((e) => Map<String, dynamic>.from(e)).toList() ?? [];
|
response?.map((e) => Map<String, dynamic>.from(e)).toList() ?? [];
|
||||||
});
|
});
|
||||||
@ -312,6 +333,7 @@ class DashboardController extends GetxController {
|
|||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
);
|
);
|
||||||
|
// OPTIMIZED: Used null-aware assignment
|
||||||
expenseTypeReportData.value =
|
expenseTypeReportData.value =
|
||||||
(response?.success == true) ? response!.data : null;
|
(response?.success == true) ? response!.data : null;
|
||||||
});
|
});
|
||||||
@ -329,7 +351,7 @@ class DashboardController extends GetxController {
|
|||||||
.map((d) => ChartTaskData.fromProjectData(d))
|
.map((d) => ChartTaskData.fromProjectData(d))
|
||||||
.toList();
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
projectChartData.clear();
|
projectChartData.clear(); // OPTIMIZED: Clear data on failure
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -338,6 +360,7 @@ class DashboardController extends GetxController {
|
|||||||
await _executeApiCall(isTasksLoading, () async {
|
await _executeApiCall(isTasksLoading, () async {
|
||||||
final response = await ApiService.getDashboardTasks(projectId: projectId);
|
final response = await ApiService.getDashboardTasks(projectId: projectId);
|
||||||
if (response?.success == true) {
|
if (response?.success == true) {
|
||||||
|
// OPTIMIZED: Used null-aware access with default value
|
||||||
totalTasks.value = response!.data?.totalTasks ?? 0;
|
totalTasks.value = response!.data?.totalTasks ?? 0;
|
||||||
completedTasks.value = response.data?.completedTasks ?? 0;
|
completedTasks.value = response.data?.completedTasks ?? 0;
|
||||||
} else {
|
} else {
|
||||||
@ -351,6 +374,7 @@ class DashboardController extends GetxController {
|
|||||||
await _executeApiCall(isTeamsLoading, () async {
|
await _executeApiCall(isTeamsLoading, () async {
|
||||||
final response = await ApiService.getDashboardTeams(projectId: projectId);
|
final response = await ApiService.getDashboardTeams(projectId: projectId);
|
||||||
if (response?.success == true) {
|
if (response?.success == true) {
|
||||||
|
// OPTIMIZED: Used null-aware access with default value
|
||||||
totalEmployees.value = response!.data?.totalEmployees ?? 0;
|
totalEmployees.value = response!.data?.totalEmployees ?? 0;
|
||||||
inToday.value = response.data?.inToday ?? 0;
|
inToday.value = response.data?.inToday ?? 0;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -44,20 +44,25 @@ class AddExpenseController extends GetxController {
|
|||||||
TextEditingController get noOfPersonsController => controllers[7];
|
TextEditingController get noOfPersonsController => controllers[7];
|
||||||
TextEditingController get employeeSearchController => controllers[8];
|
TextEditingController get employeeSearchController => controllers[8];
|
||||||
|
|
||||||
|
final List<String> _transactionIdExemptIds = const [
|
||||||
|
'24e6b0df-7929-47d2-88a3-4cf14c1f28f9',
|
||||||
|
'48d9b462-5d87-4dec-8dec-2bc943943172',
|
||||||
|
'f67beee6-6763-4108-922c-03bd86b9178d',
|
||||||
|
];
|
||||||
|
|
||||||
// --- Reactive State ---
|
// --- Reactive State ---
|
||||||
final isLoading = false.obs;
|
final isLoading = false.obs;
|
||||||
final isSubmitting = false.obs;
|
final isSubmitting = false.obs;
|
||||||
final isFetchingLocation = false.obs;
|
final isFetchingLocation = false.obs;
|
||||||
final isEditMode = false.obs;
|
final isEditMode = false.obs;
|
||||||
final isSearchingEmployees = false.obs;
|
final isSearchingEmployees = false.obs;
|
||||||
|
final isTransactionIdExempted = false.obs;
|
||||||
|
|
||||||
// --- Paid By (Single + Multi Selection Support) ---
|
// --- Paid By (Single + Multi Selection Support) ---
|
||||||
|
|
||||||
// single selection
|
// single selection
|
||||||
final selectedPaidBy = Rxn<EmployeeModel>();
|
final selectedPaidBy = Rxn<EmployeeModel>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// helper setters
|
// helper setters
|
||||||
void setSelectedPaidBy(EmployeeModel? emp) {
|
void setSelectedPaidBy(EmployeeModel? emp) {
|
||||||
selectedPaidBy.value = emp;
|
selectedPaidBy.value = emp;
|
||||||
@ -66,7 +71,6 @@ class AddExpenseController extends GetxController {
|
|||||||
// --- Dropdown Selections & Data ---
|
// --- Dropdown Selections & Data ---
|
||||||
final selectedPaymentMode = Rxn<PaymentModeModel>();
|
final selectedPaymentMode = Rxn<PaymentModeModel>();
|
||||||
final selectedExpenseType = Rxn<ExpenseTypeModel>();
|
final selectedExpenseType = Rxn<ExpenseTypeModel>();
|
||||||
// final selectedPaidBy = Rxn<EmployeeModel>();
|
|
||||||
final selectedProject = ''.obs;
|
final selectedProject = ''.obs;
|
||||||
final selectedTransactionDate = Rxn<DateTime>();
|
final selectedTransactionDate = Rxn<DateTime>();
|
||||||
|
|
||||||
@ -93,6 +97,7 @@ class AddExpenseController extends GetxController {
|
|||||||
employeeSearchController.addListener(
|
employeeSearchController.addListener(
|
||||||
() => searchEmployees(employeeSearchController.text),
|
() => searchEmployees(employeeSearchController.text),
|
||||||
);
|
);
|
||||||
|
ever(selectedPaymentMode, (_) => _checkTransactionIdExemption());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -103,6 +108,12 @@ class AddExpenseController extends GetxController {
|
|||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _checkTransactionIdExemption() {
|
||||||
|
final selectedId = selectedPaymentMode.value?.id;
|
||||||
|
isTransactionIdExempted.value =
|
||||||
|
selectedId != null && _transactionIdExemptIds.contains(selectedId);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Employee Search ---
|
// --- Employee Search ---
|
||||||
Future<void> searchEmployees(String query) async {
|
Future<void> searchEmployees(String query) async {
|
||||||
if (query.trim().isEmpty) return employeeSearchResults.clear();
|
if (query.trim().isEmpty) return employeeSearchResults.clear();
|
||||||
@ -171,6 +182,7 @@ class AddExpenseController extends GetxController {
|
|||||||
expenseTypes.firstWhereOrNull((e) => e.id == data['expensesTypeId']);
|
expenseTypes.firstWhereOrNull((e) => e.id == data['expensesTypeId']);
|
||||||
selectedPaymentMode.value =
|
selectedPaymentMode.value =
|
||||||
paymentModes.firstWhereOrNull((e) => e.id == data['paymentModeId']);
|
paymentModes.firstWhereOrNull((e) => e.id == data['paymentModeId']);
|
||||||
|
_checkTransactionIdExemption();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setPaidBy(Map<String, dynamic> data) async {
|
Future<void> _setPaidBy(Map<String, dynamic> data) async {
|
||||||
@ -536,6 +548,11 @@ class AddExpenseController extends GetxController {
|
|||||||
if (amountController.text.trim().isEmpty) missing.add("Amount");
|
if (amountController.text.trim().isEmpty) missing.add("Amount");
|
||||||
if (descriptionController.text.trim().isEmpty) missing.add("Description");
|
if (descriptionController.text.trim().isEmpty) missing.add("Description");
|
||||||
|
|
||||||
|
if (!isTransactionIdExempted.value &&
|
||||||
|
transactionIdController.text.trim().isEmpty) {
|
||||||
|
missing.add("Transaction ID");
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedTransactionDate.value == null) {
|
if (selectedTransactionDate.value == null) {
|
||||||
missing.add("Transaction Date");
|
missing.add("Transaction Date");
|
||||||
} else if (selectedTransactionDate.value!.isAfter(DateTime.now())) {
|
} else if (selectedTransactionDate.value!.isAfter(DateTime.now())) {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
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";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1718,13 +1718,20 @@ class ApiService {
|
|||||||
|
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
final json =
|
final jsonResponse = _parseAndDecryptResponse(
|
||||||
jsonDecode(response.body); // Non-encrypted body expected for this path?
|
response,
|
||||||
|
label: "Create Employee",
|
||||||
// Assuming we need to check the raw JSON status since the model is unclear
|
returnFullResponse: true,
|
||||||
|
);
|
||||||
|
if (jsonResponse != null && jsonResponse['success'] == true) {
|
||||||
return {
|
return {
|
||||||
"success": response.statusCode == 200 && (json['success'] == true),
|
"success": true,
|
||||||
"data": json,
|
"data": jsonResponse,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"success": false,
|
||||||
|
"data": jsonResponse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,7 @@ class _RegularizeActionButtonState extends State<RegularizeActionButton> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
widget.attendanceController.fetchEmployeesByProject(selectedProjectId);
|
widget.attendanceController.fetchTodaysAttendance(selectedProjectId);
|
||||||
widget.attendanceController.fetchAttendanceLogs(selectedProjectId);
|
widget.attendanceController.fetchAttendanceLogs(selectedProjectId);
|
||||||
await widget.attendanceController
|
await widget.attendanceController
|
||||||
.fetchRegularizationLogs(selectedProjectId);
|
.fetchRegularizationLogs(selectedProjectId);
|
||||||
|
|||||||
@ -52,6 +52,14 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet>
|
|||||||
final GlobalKey _expenseTypeDropdownKey = GlobalKey();
|
final GlobalKey _expenseTypeDropdownKey = GlobalKey();
|
||||||
final GlobalKey _paymentModeDropdownKey = GlobalKey();
|
final GlobalKey _paymentModeDropdownKey = GlobalKey();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.isEdit && widget.existingExpense != null) {
|
||||||
|
controller.populateFieldsForEdit(widget.existingExpense!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _showEmployeeList() async {
|
Future<void> _showEmployeeList() async {
|
||||||
final result = await showModalBottomSheet<dynamic>(
|
final result = await showModalBottomSheet<dynamic>(
|
||||||
context: context,
|
context: context,
|
||||||
@ -217,13 +225,6 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
_gap(),
|
_gap(),
|
||||||
_buildTextFieldSection(
|
|
||||||
icon: Icons.confirmation_number_outlined,
|
|
||||||
title: "GST No.",
|
|
||||||
controller: controller.gstController,
|
|
||||||
hint: "Enter GST No.",
|
|
||||||
),
|
|
||||||
_gap(),
|
|
||||||
_buildDropdownField<PaymentModeModel>(
|
_buildDropdownField<PaymentModeModel>(
|
||||||
icon: Icons.payment,
|
icon: Icons.payment,
|
||||||
title: "Payment Mode",
|
title: "Payment Mode",
|
||||||
@ -239,6 +240,29 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet>
|
|||||||
dropdownKey: _paymentModeDropdownKey,
|
dropdownKey: _paymentModeDropdownKey,
|
||||||
),
|
),
|
||||||
_gap(),
|
_gap(),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.isTransactionIdExempted.value) {
|
||||||
|
return const SizedBox.shrink(); // hide field
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildTextFieldSection(
|
||||||
|
icon: Icons.confirmation_number_outlined,
|
||||||
|
title: "Transaction ID",
|
||||||
|
hint: "Enter Transaction ID",
|
||||||
|
controller: controller.transactionIdController,
|
||||||
|
isRequiredOverride: true,
|
||||||
|
validator: (v) {
|
||||||
|
return (v != null && v.isNotEmpty)
|
||||||
|
? Validators.transactionIdValidator(v)
|
||||||
|
: Validators.requiredField(v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_gap(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
_buildPaidBySection(),
|
_buildPaidBySection(),
|
||||||
_gap(),
|
_gap(),
|
||||||
_buildTextFieldSection(
|
_buildTextFieldSection(
|
||||||
@ -262,12 +286,9 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet>
|
|||||||
_gap(),
|
_gap(),
|
||||||
_buildTextFieldSection(
|
_buildTextFieldSection(
|
||||||
icon: Icons.confirmation_number_outlined,
|
icon: Icons.confirmation_number_outlined,
|
||||||
title: "Transaction ID",
|
title: "GST No.",
|
||||||
controller: controller.transactionIdController,
|
controller: controller.gstController,
|
||||||
hint: "Enter Transaction ID",
|
hint: "Enter GST No.",
|
||||||
validator: (v) => (v != null && v.isNotEmpty)
|
|
||||||
? Validators.transactionIdValidator(v)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
_gap(),
|
_gap(),
|
||||||
_buildTransactionDateField(),
|
_buildTransactionDateField(),
|
||||||
@ -321,12 +342,18 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet>
|
|||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
FormFieldValidator<String>? validator,
|
FormFieldValidator<String>? validator,
|
||||||
int maxLines = 1,
|
int maxLines = 1,
|
||||||
|
bool? isRequiredOverride,
|
||||||
}) {
|
}) {
|
||||||
|
final bool isRequired = isRequiredOverride ?? (validator != null);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SectionTitle(
|
SectionTitle(
|
||||||
icon: icon, title: title, requiredField: validator != null),
|
icon: icon,
|
||||||
|
title: title,
|
||||||
|
requiredField: isRequired
|
||||||
|
),
|
||||||
MySpacing.height(6),
|
MySpacing.height(6),
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
|||||||
@ -123,14 +123,33 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen>
|
|||||||
Widget _buildWelcomeText() {
|
Widget _buildWelcomeText() {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
MyText(
|
RichText(
|
||||||
"Welcome to On Field Work",
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 600,
|
|
||||||
color: Colors.black87,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
|
children: const [
|
||||||
|
TextSpan(
|
||||||
|
text: "Welcome to ",
|
||||||
|
style: TextStyle(color: Colors.black87),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "OnField",
|
||||||
|
style: TextStyle(color: Color(0xFF007BFF)), // Blue
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "Work",
|
||||||
|
style: TextStyle(color: Color(0xFF71DD37)), // Green
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
MyText(
|
MyText(
|
||||||
"Streamline Project Management\nBoost Productivity with Automation.",
|
"Streamline Project Management\nBoost Productivity with Automation.",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@ -254,7 +273,11 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen>
|
|||||||
Widget _buildBackButton() {
|
Widget _buildBackButton() {
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
onPressed: () async => await LocalStorage.logout(),
|
onPressed: () async => await LocalStorage.logout(),
|
||||||
icon: Icon(Icons.arrow_back, size: 18, color: contentTheme.primary,),
|
icon: Icon(
|
||||||
|
Icons.arrow_back,
|
||||||
|
size: 18,
|
||||||
|
color: contentTheme.primary,
|
||||||
|
),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
'Back to Login',
|
'Back to Login',
|
||||||
color: contentTheme.primary,
|
color: contentTheme.primary,
|
||||||
|
|||||||
@ -196,14 +196,33 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
Widget _buildWelcomeText() {
|
Widget _buildWelcomeText() {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
MyText(
|
RichText(
|
||||||
"Welcome to On Field Work",
|
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: 800,
|
|
||||||
color: Colors.black87,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
|
children: const [
|
||||||
|
TextSpan(
|
||||||
|
text: "Welcome to ",
|
||||||
|
style: TextStyle(color: Colors.black87),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "OnField",
|
||||||
|
style: TextStyle(color: Color(0xFF007BFF)), // Blue
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "Work",
|
||||||
|
style: TextStyle(color: Color(0xFF71DD37)), // Green
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
MyText(
|
MyText(
|
||||||
"Streamline Project Management\nBoost Productivity with Automation.",
|
"Streamline Project Management\nBoost Productivity with Automation.",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import 'package:on_field_work/helpers/services/api_endpoints.dart';
|
|||||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/wave_background.dart';
|
import 'package:on_field_work/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
|
|
||||||
class MPINAuthScreen extends StatefulWidget {
|
class MPINAuthScreen extends StatefulWidget {
|
||||||
const MPINAuthScreen({super.key});
|
const MPINAuthScreen({super.key});
|
||||||
|
|
||||||
@ -91,12 +90,31 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
MyText(
|
RichText(
|
||||||
"Welcome to On Field Work",
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 800,
|
|
||||||
color: Colors.black87,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
text: const TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "Welcome to ",
|
||||||
|
style: TextStyle(color: Colors.black87),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "OnField",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF007BFF)), // Blue
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "Work",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF71DD37)), // Green
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
MyText(
|
MyText(
|
||||||
@ -317,8 +335,8 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
if (isNewUser || isChangeMpin)
|
if (isNewUser || isChangeMpin)
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => Get.toNamed('/dashboard'),
|
onPressed: () => Get.toNamed('/dashboard'),
|
||||||
icon: Icon(Icons.arrow_back,
|
icon:
|
||||||
size: 18, color: contentTheme.primary),
|
Icon(Icons.arrow_back, size: 18, color: contentTheme.primary),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
'Back to Home Page',
|
'Back to Home Page',
|
||||||
color: contentTheme.primary,
|
color: contentTheme.primary,
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
final AttendanceController attendanceController =
|
final AttendanceController attendanceController =
|
||||||
Get.put(AttendanceController());
|
Get.put(AttendanceController());
|
||||||
final DynamicMenuController menuController = Get.put(DynamicMenuController());
|
final DynamicMenuController menuController = Get.put(DynamicMenuController());
|
||||||
final ProjectController projectController = Get.find<ProjectController>();
|
final ProjectController projectController = Get.put(ProjectController());
|
||||||
|
|
||||||
bool hasMpin = true;
|
bool hasMpin = true;
|
||||||
|
|
||||||
@ -56,19 +56,22 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
Widget _cardWrapper({required Widget child}) {
|
Widget _cardWrapper({required Widget child}) {
|
||||||
|
const BorderRadius cardRadius = BorderRadius.all(Radius.circular(5));
|
||||||
|
const List<BoxShadow> cardShadow = [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(0, 0, 0, 0.05),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: Offset(0, 4),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: cardRadius,
|
||||||
border: Border.all(color: Colors.black12.withOpacity(.04)),
|
border: Border.all(color: Colors.black12.withOpacity(.04)),
|
||||||
boxShadow: [
|
boxShadow: cardShadow,
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black12.withOpacity(.05),
|
|
||||||
blurRadius: 12,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
@ -77,14 +80,12 @@ 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),
|
||||||
child: Text(
|
// OPTIMIZATION: Use MyText for consistent styling
|
||||||
|
child: MyText.titleMedium(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
fontWeight: 700,
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +121,32 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: Column(
|
||||||
'No attendance data available',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: TextStyle(color: Colors.white),
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 30, color: Colors.white),
|
||||||
|
MySpacing.width(10),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"No attendance data available yet.",
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MySpacing.height(12),
|
||||||
|
Text(
|
||||||
|
"You are not added to this project or attendance data is not available.",
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 13),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -137,6 +161,12 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
? 'Checked In'
|
? 'Checked In'
|
||||||
: 'Checked Out';
|
: 'Checked Out';
|
||||||
|
|
||||||
|
final String infoText = !isCheckedIn
|
||||||
|
? 'You are not checked-in yet. Please check-in to start your work.'
|
||||||
|
: !isCheckedOut
|
||||||
|
? 'You are currently checked-in. Don\'t forget to check-out after your work.'
|
||||||
|
: 'You have checked-out for today.';
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -185,19 +215,15 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
MySpacing.height(12), // OPTIMIZED
|
||||||
Text(
|
Text(
|
||||||
!isCheckedIn
|
infoText,
|
||||||
? 'You are not checked-in yet. Please check-in to start your work.'
|
|
||||||
: !isCheckedOut
|
|
||||||
? 'You are currently checked-in. Don\'t forget to check-out after your work.'
|
|
||||||
: 'You have checked-out for today.',
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white70,
|
color: Colors.white70,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
MySpacing.height(12), // OPTIMIZED
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
@ -236,8 +262,8 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
|
|
||||||
final bool projectSelected = projectController.selectedProject != null;
|
final bool projectSelected = projectController.selectedProject != null;
|
||||||
|
|
||||||
// these are String constants from permission_constants.dart
|
// These are String constants from permission_constants.dart (kept outside of Obx)
|
||||||
final List<String> cardOrder = [
|
const List<String> cardOrder = [
|
||||||
MenuItems.attendance,
|
MenuItems.attendance,
|
||||||
MenuItems.employees,
|
MenuItems.employees,
|
||||||
MenuItems.directory,
|
MenuItems.directory,
|
||||||
@ -247,6 +273,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
MenuItems.infraProjects,
|
MenuItems.infraProjects,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// OPTIMIZATION: Using a static map for meta data
|
||||||
final Map<String, _DashboardCardMeta> meta = {
|
final Map<String, _DashboardCardMeta> meta = {
|
||||||
MenuItems.attendance:
|
MenuItems.attendance:
|
||||||
_DashboardCardMeta(LucideIcons.scan_face, contentTheme.success),
|
_DashboardCardMeta(LucideIcons.scan_face, contentTheme.success),
|
||||||
@ -264,6 +291,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
_DashboardCardMeta(LucideIcons.building_2, contentTheme.primary),
|
_DashboardCardMeta(LucideIcons.building_2, contentTheme.primary),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// OPTIMIZATION: Use map for faster lookup, then filter the preferred order
|
||||||
final Map<String, dynamic> allowed = {
|
final Map<String, dynamic> allowed = {
|
||||||
for (final m in menuController.menuItems)
|
for (final m in menuController.menuItems)
|
||||||
if (m.available && meta.containsKey(m.id)) m.id: m,
|
if (m.available && meta.containsKey(m.id)) m.id: m,
|
||||||
@ -280,14 +308,8 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
_sectionTitle(
|
||||||
'Modules',
|
'Modules'), // OPTIMIZATION: Reused section title helper
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!projectSelected)
|
if (!projectSelected)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@ -326,8 +348,9 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
final item = allowed[id]!;
|
final item = allowed[id]!;
|
||||||
final _DashboardCardMeta cardMeta = meta[id]!;
|
final _DashboardCardMeta cardMeta = meta[id]!;
|
||||||
|
|
||||||
|
// Attendance is the only module not requiring a project
|
||||||
final bool isEnabled =
|
final bool isEnabled =
|
||||||
item.name == 'Attendance' ? true : projectSelected;
|
item.id == MenuItems.attendance ? true : projectSelected;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -371,7 +394,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
color:
|
color:
|
||||||
isEnabled ? cardMeta.color : Colors.grey.shade300,
|
isEnabled ? cardMeta.color : Colors.grey.shade300,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
MySpacing.height(6), // OPTIMIZED
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -413,9 +436,14 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
final String? selectedId = projectController.selectedProjectId.value;
|
final String? selectedId = projectController.selectedProjectId.value;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
// Use the new specialized skeleton
|
|
||||||
return SkeletonLoaders.projectSelectorSkeleton();
|
return SkeletonLoaders.projectSelectorSkeleton();
|
||||||
}
|
}
|
||||||
|
final String selectedProjectName = projects
|
||||||
|
.firstWhereOrNull(
|
||||||
|
(p) => p.id == selectedId,
|
||||||
|
)
|
||||||
|
?.name ??
|
||||||
|
'Select Project';
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -444,15 +472,10 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
MySpacing.width(12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
projects
|
selectedProjectName,
|
||||||
.firstWhereOrNull(
|
|
||||||
(p) => p.id == selectedId,
|
|
||||||
)
|
|
||||||
?.name ??
|
|
||||||
'Select Project',
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -497,17 +520,18 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
const TextField(
|
||||||
|
// OPTIMIZED: Added const
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search project...',
|
hintText: 'Search project...',
|
||||||
isDense: true,
|
isDense: true,
|
||||||
prefixIcon: const Icon(Icons.search),
|
prefixIcon: Icon(Icons.search),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
MySpacing.height(10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: projects.length,
|
itemCount: projects.length,
|
||||||
@ -554,7 +578,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
_dashboardModules(),
|
_dashboardModules(),
|
||||||
MySpacing.height(20),
|
MySpacing.height(20),
|
||||||
_sectionTitle('Reports & Analytics'),
|
_sectionTitle('Reports & Analytics'),
|
||||||
CompactPurchaseInvoiceDashboard(),
|
const CompactPurchaseInvoiceDashboard(),
|
||||||
MySpacing.height(20),
|
MySpacing.height(20),
|
||||||
CollectionsHealthWidget(),
|
CollectionsHealthWidget(),
|
||||||
MySpacing.height(20),
|
MySpacing.height(20),
|
||||||
|
|||||||
@ -183,7 +183,13 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
|||||||
return canCreate
|
return canCreate
|
||||||
? FloatingActionButton.extended(
|
? FloatingActionButton.extended(
|
||||||
backgroundColor: contentTheme.primary,
|
backgroundColor: contentTheme.primary,
|
||||||
onPressed: showPaymentRequestBottomSheet,
|
onPressed: () {
|
||||||
|
showPaymentRequestBottomSheet(
|
||||||
|
onUpdated: () async {
|
||||||
|
await paymentController.fetchPaymentRequests();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
icon: const Icon(Icons.add, color: Colors.white),
|
icon: const Icon(Icons.add, color: Colors.white),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
"Create Payment Request",
|
"Create Payment Request",
|
||||||
|
|||||||
@ -67,7 +67,6 @@ class _SplashScreenState extends State<SplashScreen>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Floating effect: from -8.0 to 8.0 (loops repeatedly after initial animations)
|
// Floating effect: from -8.0 to 8.0 (loops repeatedly after initial animations)
|
||||||
_floatAnimation = Tween<double>(begin: -8.0, end: 8.0).animate(
|
_floatAnimation = Tween<double>(begin: -8.0, end: 8.0).animate(
|
||||||
CurvedAnimation(
|
CurvedAnimation(
|
||||||
@ -116,32 +115,38 @@ class _SplashScreenState extends State<SplashScreen>
|
|||||||
],
|
],
|
||||||
stops: const [0.3, 0.5, 0.7], // Position of colors
|
stops: const [0.3, 0.5, 0.7], // Position of colors
|
||||||
// The begin/end points move based on the animation value
|
// The begin/end points move based on the animation value
|
||||||
begin: Alignment(_shimmerAnimation.value - 1.0, 0.0), // Start from left
|
begin:
|
||||||
|
Alignment(_shimmerAnimation.value - 1.0, 0.0), // Start from left
|
||||||
end: Alignment(_shimmerAnimation.value, 0.0), // End to right
|
end: Alignment(_shimmerAnimation.value, 0.0), // End to right
|
||||||
);
|
);
|
||||||
|
|
||||||
// The Text Content: RichText allows for different styles within one text block
|
// The Text Content: RichText allows for different styles within one text block
|
||||||
return RichText(
|
return RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: textStyle.copyWith(color: Colors.black), // Base style
|
style: textStyle.copyWith(color: Colors.black),
|
||||||
children: <TextSpan>[
|
children: <TextSpan>[
|
||||||
// 'On' - Blue color
|
// 'OnField' - Blue (#007bff)
|
||||||
TextSpan(
|
const TextSpan(
|
||||||
text: 'On',
|
text: 'OnField',
|
||||||
style: TextStyle(color: Colors.blueAccent.shade700),
|
style: TextStyle(
|
||||||
|
color: Color(0xFF007BFF),
|
||||||
),
|
),
|
||||||
// 'FieldWork' - Green color
|
|
||||||
TextSpan(
|
|
||||||
text: 'FieldWork',
|
|
||||||
style: TextStyle(color: Colors.green.shade700),
|
|
||||||
),
|
),
|
||||||
// '.com' - The part that uses the animated gradient
|
|
||||||
|
// 'Work' - Green (#71dd37)
|
||||||
|
const TextSpan(
|
||||||
|
text: 'Work',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF71DD37),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// '.com' - Shimmer gradient
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '.com',
|
text: '.com',
|
||||||
style: textStyle.copyWith(
|
style: textStyle.copyWith(
|
||||||
// Use a Paint()..shader to apply the gradient to the text color
|
foreground: Paint()
|
||||||
// The Rect size (150.0 x 50.0) must be large enough to cover the '.com' text
|
..shader = shimmerGradient.createShader(
|
||||||
foreground: Paint()..shader = shimmerGradient.createShader(
|
|
||||||
const Rect.fromLTWH(0.0, 0.0, 150.0, 50.0),
|
const Rect.fromLTWH(0.0, 0.0, 150.0, 50.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -202,7 +207,8 @@ class _SplashScreenState extends State<SplashScreen>
|
|||||||
child: _buildAnimatedDomainText(),
|
child: _buildAnimatedDomainText(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10), // Small space between new text and message
|
const SizedBox(
|
||||||
|
height: 10), // Small space between new text and message
|
||||||
|
|
||||||
// Text Message (Fades in slightly after logo)
|
// Text Message (Fades in slightly after logo)
|
||||||
if (widget.message != null)
|
if (widget.message != null)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user