...@@ -16,6 +16,7 @@ class HomescreenNotifier extends ChangeNotifier { ...@@ -16,6 +16,7 @@ class HomescreenNotifier extends ChangeNotifier {
String _curdate = ""; String _curdate = "";
String _empId = ""; String _empId = "";
String _session = ""; String _session = "";
String _requestId = "";
String _webPageUrl = ""; String _webPageUrl = "";
String _whizzdomPageUrl = ""; String _whizzdomPageUrl = "";
String _roleStatus = ""; String _roleStatus = "";
...@@ -34,6 +35,8 @@ class HomescreenNotifier extends ChangeNotifier { ...@@ -34,6 +35,8 @@ class HomescreenNotifier extends ChangeNotifier {
String get session => _session; String get session => _session;
String get requestId => _requestId;
String get webPageUrl => _webPageUrl; String get webPageUrl => _webPageUrl;
String get whizzdomPageUrl => _whizzdomPageUrl; String get whizzdomPageUrl => _whizzdomPageUrl;
...@@ -68,6 +71,7 @@ class HomescreenNotifier extends ChangeNotifier { ...@@ -68,6 +71,7 @@ class HomescreenNotifier extends ChangeNotifier {
_email = await SharedpreferencesService().getString("UserEmail") ?? ""; _email = await SharedpreferencesService().getString("UserEmail") ?? "";
_session = await SharedpreferencesService().getString("Session_id") ?? ""; _session = await SharedpreferencesService().getString("Session_id") ?? "";
_roleStatus = await SharedpreferencesService().getString("roles") ?? ""; _roleStatus = await SharedpreferencesService().getString("roles") ?? "";
_requestId = await SharedpreferencesService().getString("attendRequestId") ?? "";
var lastLocationTime = await SharedpreferencesService().getString( var lastLocationTime = await SharedpreferencesService().getString(
"lastLocationTime", "lastLocationTime",
); );
......
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/leaveApplicationDetailsResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class LeaveApplicationDetailsProvider extends ChangeNotifier {
leaveApplicationDetailsResponse? _response;
bool _isLoading = false;
String? _errorMessage;
leaveApplicationDetailsResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
/// Fetch leave application details
Future<void> fetchLeaveApplicationDetails(BuildContext context, String leaveRequestId) async {
_isLoading = true;
_errorMessage = null;
_response = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.leaveApplicationDetailAPI(
provider.session,
provider.empId,
leaveRequestId,
);
if (result != null) {
_response = result;
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Something went wrong: $e";
debugPrint('Error fetching leave application details: $e');
}
_isLoading = false;
notifyListeners();
}
/// Clear the current response data
void clearData() {
_response = null;
_errorMessage = null;
notifyListeners();
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:generp/Models/hrmModels/attendanceRequestDetailsResponse.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/attendanceRequestListResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class AttendanceDetailsProvider extends ChangeNotifier {
attendanceRequestDetailsResponse? _response;
bool _isLoading = false;
String? _errorMessage;
attendanceRequestDetailsResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
Future<void> fetchAttendanceRequestDetail(context,String requestId) async {
_isLoading = true;
_errorMessage = null;
_response = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.attendanceRequestDetailAPI(provider.empId, provider.session, requestId);
if (result != null) {
_response = result;
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Something went wrong: $e";
}
_isLoading = false;
notifyListeners();
}
}
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/attendanceRequestListResponse.dart';
import '../../Models/ordersModels/commonResponse.dart';
import '../../Utils/app_colors.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class Attendancelistprovider extends ChangeNotifier {
attendanceRequestListResponse? _response;
bool _isLoading = false;
String? _errorMessage;
attendanceRequestListResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
// Filter states
String _selectedType = "All";
String _selectedDateRange = "This Month";
DateTimeRange? _customDateRange;
String get selectedType => _selectedType;
String get selectedDateRange => _selectedDateRange;
DateTimeRange? get customDateRange => _customDateRange;
// Addition of attendance
CommonResponse? _addResponse;
CommonResponse? get addResponse => _addResponse;
bool _isSubmitting = false;
bool get isSubmitting => _isSubmitting;
// Date controllers for filter UI
final TextEditingController fromDateController = TextEditingController();
final TextEditingController toDateController = TextEditingController();
// For manual attendance date field
final TextEditingController dateController = TextEditingController();
DateTime? _selectedDate;
DateTime? get selectedDate => _selectedDate;
// Type options for filter
final List<String> typeOptions = ["All", "Check In", "Check Out", "Check In/Out"];
// Date range options for filter
final List<String> dateRangeOptions = [
"All",
"Today",
"Yesterday",
"This Month",
"Past 7 days",
"Last Month",
"Custom",
];
bool isDateValid() {
if (_selectedDate == null) return false;
DateTime today = DateTime.now();
DateTime yesterday = today.subtract(const Duration(days: 1));
// normalize (remove time part)
DateTime normalizedSelected = DateTime(_selectedDate!.year, _selectedDate!.month, _selectedDate!.day);
DateTime normalizedToday = DateTime(today.year, today.month, today.day);
DateTime normalizedYesterday = DateTime(yesterday.year, yesterday.month, yesterday.day);
return normalizedSelected == normalizedToday || normalizedSelected == normalizedYesterday;
}
String? validateManualAttendance({//its working or not
required String type,
required String? checkInTime,
required String? checkInLoc,
required File? checkInProof,
required String? checkOutTime,
required String? checkOutLoc,
required File? checkOutProof,
required String? checkInDesc,
required String? checkOutDesc,
}) {
if (!isDateValid()) {
return "Date must be today or yesterday";
}
if (type.isEmpty) return "Please select type";
if (type == "Check In") {
if ((checkInTime ?? "").isEmpty ||
(checkInLoc ?? "").isEmpty ||
(checkInDesc ?? "").isEmpty ||
checkInProof == null) {
return "Please fill all Check In fields";
}
}
if (type == "Check Out") {
if ((checkOutTime ?? "").isEmpty ||
(checkOutLoc ?? "").isEmpty ||
(checkOutDesc ?? "").isEmpty ||
checkOutProof == null) {
return "Please fill all Check Out fields";
}
}
if (type == "Check In/Out") {
if ((checkInTime ?? "").isEmpty ||
(checkInLoc ?? "").isEmpty ||
(checkInDesc ?? "").isEmpty ||
checkInProof == null ||
(checkOutTime ?? "").isEmpty ||
(checkOutLoc ?? "").isEmpty ||
(checkOutDesc ?? "").isEmpty ||
checkOutProof == null) {
return "Please fill all Check In & Check Out fields";
}
}
return null; // everything ok
}
/// Fetch attendance request list with filters
Future<void> fetchAttendanceRequests(BuildContext context,
{String? type, String? dateRange, DateTimeRange? customRange}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
// Update filter states if provided
if (type != null) _selectedType = type;
if (dateRange != null) _selectedDateRange = dateRange;
if (customRange != null) _customDateRange = customRange;
// Calculate date range based on selection
final dateParams = _getDateRangeParams(_selectedDateRange, _customDateRange);
// Convert "All" type to empty string for API
final apiType = _selectedType == "All" ? "" : _selectedType;
final result = await ApiCalling.attendanceRequestListAPI(
provider.empId,
provider.session,
apiType,
dateParams['from']!,
dateParams['to']!,
);
debugPrint('Fetching attendance from: ${dateParams['from']} to: ${dateParams['to']}');
if (result != null) {
_response = result;
if (_response?.requestList == null || _response!.requestList!.isEmpty) {
_errorMessage = "No attendance records found!";
}
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Error: $e";
debugPrint('Error fetching attendance: $e');
}
_isLoading = false;
notifyListeners();
}
/// --- Add Attendance Request ---
Future<void> addAttendanceRequest(
BuildContext context, {
required String process,
required String type,
required String loc,
required String checkDate,
String? checkInTime,
String? checkInLoc,
File? checkInProof,
String? checkOutTime,
String? checkOutLoc,
File? checkOutProof,
String? note,
}) async {
_isSubmitting = true;
_errorMessage = null;
_addResponse = null;
notifyListeners();
try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.addAttendanceRequestAPI(
sessionId: homeProvider.session,
empId: homeProvider.empId,
process: process,
type: type,
loc: loc,
checkDate: checkDate,
checkInTime: checkInTime,
checkInLoc: checkInLoc,
checkInProof: checkInProof,
checkOutTime: checkOutTime,
checkOutLoc: checkOutLoc,
checkOutProof: checkOutProof,
note: note,
);
if (result != null) {
_addResponse = result;
if (result.error != null && result.error!.isNotEmpty) {
_errorMessage = result.error;
} else {
_addResponse = result;
}
} else {
_errorMessage = "Failed to submit request!";
}
} catch (e) {
_errorMessage = "Error submitting request: $e";
}
_isSubmitting = false;
notifyListeners();
}
/// Apply filters coming from bottom sheet
void updateFiltersFromSheet(
BuildContext context, {
required String type,
required String selectedValue,
DateTimeRange? customRange,
}) {
_selectedType = type;
_selectedDateRange = selectedValue;
_customDateRange = customRange;
fetchAttendanceRequests(
context,
type: _selectedType,
dateRange: _selectedDateRange,
customRange: _customDateRange,
);
}
/// Set type filter and refresh data
void setTypeFilter(BuildContext context, String type) {
_selectedType = type;
fetchAttendanceRequests(context);
}
/// Set date range filter and refresh data
void setDateRangeFilter(BuildContext context, String dateRange,
{DateTimeRange? customRange}) {
_selectedDateRange = dateRange;
if (customRange != null) {
_customDateRange = customRange;
fromDateController.text = _formatDate(customRange.start);
toDateController.text = _formatDate(customRange.end);
}
fetchAttendanceRequests(context);
}
/// Clear all filters and refresh data
void clearFilters(BuildContext context) {
_selectedType = "All";
_selectedDateRange = "This Month";
_customDateRange = null;
fromDateController.clear();
toDateController.clear();
fetchAttendanceRequests(context);
}
/// Reset form and data
void resetForm(BuildContext context) {
_response = null;
_errorMessage = null;
clearFilters(context);
notifyListeners();
}
/// Get date range parameters for API
Map<String, String> _getDateRangeParams(String dateRange, DateTimeRange? customRange) {
final now = DateTime.now();
final formatter = DateFormat("dd MMM yyyy");
late DateTime from;
late DateTime to;
switch (dateRange) {
case "All":
from = DateTime(now.year - 1);
to = now;
break;
case "Today":
from = now;
to = now;
break;
case "Yesterday":
from = now.subtract(const Duration(days: 1));
to = now.subtract(const Duration(days: 1));
break;
case "This Month":
from = DateTime(now.year, now.month, 1);
to = DateTime(now.year, now.month + 1, 0);
break;
case "Past 7 days":
from = now.subtract(const Duration(days: 6));
to = now;
break;
case "Last Month":
from = DateTime(now.year, now.month - 1, 1);
to = DateTime(now.year, now.month, 0);
break;
case "Custom":
if (customRange != null) {
from = customRange.start;
to = customRange.end;
} else {
from = now.subtract(const Duration(days: 30));
to = now;
}
break;
default:
from = now;
to = now;
}
return {
"from": formatter.format(from),
"to": formatter.format(to),
};
}
/// Format date for display
String _formatDate(DateTime date) {
return DateFormat("dd MMM yyyy").format(date);
}
/// Apply filters and refresh data
void applyFilters(BuildContext context) {
fetchAttendanceRequests(
context,
type: _selectedType,
dateRange: _selectedDateRange,
customRange: _customDateRange,
);
}
/// Set Selected Date
void setSelectedDate(DateTime date) {
_selectedDate = date;
dateController.text = DateFormat("dd MMM yyyy").format(date);
notifyListeners();
}
/// Show Cupertino Date Picker
void showDatePickerDialog(BuildContext context) {
if (_selectedDate == null) {
setSelectedDate(DateTime.now());
}
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 250,
padding: const EdgeInsets.only(top: 6.0),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: Column(
children: [
// Cancel + Done Buttons
SizedBox(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: Text(
'Cancel',
style: TextStyle(
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
onPressed: () => Navigator.pop(context),
),
CupertinoButton(
child: Text(
'Done',
style: TextStyle(
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
onPressed: () {
setSelectedDate(_selectedDate ?? DateTime.now());
Navigator.pop(context);
},
),
],
),
),
// Cupertino Date Picker
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: _selectedDate ?? DateTime.now(),
mode: CupertinoDatePickerMode.date,
use24hFormat: true,
showDayOfWeek: true,
onDateTimeChanged: (DateTime newDate) {
setSelectedDate(newDate);
},
),
),
],
),
),
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:generp/Models/ordersModels/commonResponse.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/leaveApplicationLIstResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class LeaveApplicationListProvider extends ChangeNotifier {
leaveApplicationLIstResponse? _response;
bool _isLoading = false;
String? _errorMessage;
leaveApplicationLIstResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
// Filter states
String _selectedStatus = "All";
String _selectedDateRange = "This Month";
DateTimeRange? _customDateRange;
String get selectedStatus => _selectedStatus;
String get selectedDateRange => _selectedDateRange;
DateTimeRange? get customDateRange => _customDateRange;
// Date controllers for filter UI
final TextEditingController fromDateController = TextEditingController();
final TextEditingController toDateController = TextEditingController();
// Controllers for Add Leave form
final TextEditingController fromDateField = TextEditingController();
final TextEditingController toDateField = TextEditingController();
final TextEditingController fromTimeField = TextEditingController();
final TextEditingController toTimeField = TextEditingController();
final TextEditingController reasonController = TextEditingController();
// Status options for filter
final List<String> statusOptions = ["All", "Requested", "Approved", "Rejected"];
// Date range options for filter
final List<String> dateRangeOptions = [
"All",
"Today",
"Yesterday",
"This Month",
"Past 7 days",
"Last Month",
"Custom",
];
// Loading state for Add Leave
bool _isSubmitting = false;
bool get isSubmitting => _isSubmitting;
CommonResponse? _addResponse;
CommonResponse? get addResponse => _addResponse;
DateTime? _selectedDate;
DateTime? get selectedDate => _selectedDate;
/// Fetch leave application list with filters
Future<void> fetchLeaveApplications(BuildContext context,
{String? status, String? dateRange, DateTimeRange? customRange}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final filterStatus = status ?? _selectedStatus;
final filterDateRange = dateRange ?? _selectedDateRange;
final filterCustomRange = customRange ?? _customDateRange;
final dateParams = _getDateRangeParams(filterDateRange, filterCustomRange);
final result = await ApiCalling.leaveApplicationListAPI(
provider.session,
provider.empId,
dateParams['from']!,
dateParams['to']!,
);
debugPrint(
'Fetching leave applications from: ${dateParams['from']} to: ${dateParams['to']}');
if (result != null) {
_response = result;
if (filterStatus != "All" && _response?.requestList != null) {
_response!.requestList = _response!.requestList!
.where((item) =>
item.status?.toLowerCase() == filterStatus.toLowerCase())
.toList();
}
if (_response?.requestList == null ||
_response!.requestList!.isEmpty) {
_errorMessage = "No leave applications found!";
}
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Error: $e";
debugPrint('Error fetching leave applications: $e');
}
_isLoading = false;
notifyListeners();
}
/// --- Add Leave Request ---
Future<void> addLeaveRequest(
BuildContext context, {
required String fromDate,
required String fromTime,
required String toDate,
required String toTime,
required String leaveType,
required String reason,
}) async {
_isSubmitting = true;
_errorMessage = null;
_addResponse = null;
notifyListeners();
try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.leaveRequestAddAPI(
homeProvider.session,
homeProvider.empId,
fromDate,
fromTime,
toDate,
toTime,
leaveType,
reason,
);
if (result != null) {
_addResponse = result;
if (result.error != null && result.error!.isNotEmpty) {
_errorMessage = result.error;
}
} else {
_errorMessage = "Failed to submit leave request!";
}
} catch (e) {
_errorMessage = "Error submitting leave request: $e";
}
_isSubmitting = false;
notifyListeners();
}
/// Set status filter
void setStatusFilter(String status) {
_selectedStatus = status;
notifyListeners();
}
/// Set date range filter
void setDateRangeFilter(String dateRange, {DateTimeRange? customRange}) {
_selectedDateRange = dateRange;
if (customRange != null) {
_customDateRange = customRange;
fromDateController.text = _formatDate(customRange.start);
toDateController.text = _formatDate(customRange.end);
}
notifyListeners();
}
/// Clear all filters
void clearFilters() {
_selectedStatus = "All";
_selectedDateRange = "This Month";
_customDateRange = null;
fromDateController.clear();
toDateController.clear();
notifyListeners();
}
/// Reset form and data
void resetForm() {
_response = null;
_errorMessage = null;
clearFilters();
notifyListeners();
}
/// Get date range parameters for API
Map<String, String> _getDateRangeParams(
String dateRange, DateTimeRange? customRange) {
final now = DateTime.now();
final formatter = DateFormat("dd MMM yyyy");
late DateTime from;
late DateTime to;
switch (dateRange) {
case "All":
from = DateTime(now.year - 1);
to = now;
break;
case "Today":
from = now;
to = now;
break;
case "Yesterday":
from = now.subtract(const Duration(days: 1));
to = now.subtract(const Duration(days: 1));
break;
case "This Month":
from = DateTime(now.year, now.month, 1);
to = DateTime(now.year, now.month + 1, 0);
break;
case "Past 7 days":
from = now.subtract(const Duration(days: 6));
to = now;
break;
case "Last Month":
from = DateTime(now.year, now.month - 1, 1);
to = DateTime(now.year, now.month, 0);
break;
case "Custom":
if (customRange != null) {
from = customRange.start;
to = customRange.end;
} else {
from = now.subtract(const Duration(days: 30));
to = now;
}
break;
default:
from = now;
to = now;
}
return {
"from": formatter.format(from),
"to": formatter.format(to),
};
}
/// Format date
String _formatDate(DateTime date) {
return DateFormat("dd MMM yyyy").format(date);
}
/// Show Cupertino DatePicker for leave form
void showDatePickerDialog(BuildContext context,
{bool isFromDate = true}) {
DateTime? currentDate = DateTime.now();
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 250,
padding: const EdgeInsets.only(top: 6.0),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: Column(
children: [
SizedBox(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: const Text("Cancel",
style: TextStyle(color: Colors.blue)),
onPressed: () => Navigator.pop(context),
),
CupertinoButton(
child: const Text("Done",
style: TextStyle(color: Colors.blue)),
onPressed: () {
if (isFromDate) {
fromDateField.text =
_formatDate(currentDate ?? DateTime.now());
} else {
toDateField.text =
_formatDate(currentDate ?? DateTime.now());
}
Navigator.pop(context);
},
),
],
),
),
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: currentDate,
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
currentDate = newDate;
},
),
),
],
),
),
),
);
}
/// Apply filters
void applyFilters(BuildContext context) {
fetchLeaveApplications(
context,
status: _selectedStatus,
dateRange: _selectedDateRange,
customRange: _customDateRange,
);
}
/// Export
List<List<String>> prepareExportData() {
final headers = [
'ID',
'Applied Date',
'From Date',
'To Date',
'Leave Type',
'Status',
'Reason',
];
if (_response?.requestList == null) {
return [headers];
}
final rows = _response!.requestList!.map((item) => [
item.id ?? '',
item.appliedDate ?? '',
item.fromPeriod ?? '',
item.toPeriod ?? '',
'', // leave type if available
item.status ?? '',
'', // reason if available
]).toList();
return [headers, ...rows];
}
/// Set selected single date (if needed)
void setSelectedDate(DateTime date) {
_selectedDate = date;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/rewardListResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class RewardListProvider extends ChangeNotifier {
rewardListResponse? _response;
bool _isLoading = false;
String? _errorMessage;
/// Getters
rewardListResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
/// Fetch Reward List
Future<void> fetchRewardList(BuildContext context) async {
_isLoading = true;
_errorMessage = null;
_response = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.rewardListAPI(
provider.empId,
provider.session,
);
if (result != null) {
_response = result;
} else {
_errorMessage = "No reward data found!";
}
} catch (e) {
_errorMessage = "Something went wrong: $e";
}
_isLoading = false;
notifyListeners();
}
}
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/tourExpensesDetailsResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class TourExpensesDetailsProvider extends ChangeNotifier {
tourExpensesDetailsResponse? _response;
bool _isLoading = false;
String? _errorMessage;
tourExpensesDetailsResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
Future<void> fetchTourExpensesDetails(BuildContext context, String tourBillId) async {
_isLoading = true;
_errorMessage = null;
_response = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.tourExpensesDetailAPI(
provider.session,
provider.empId,
tourBillId,
);
print("==== Tour Submitted ====");
print("empId: "+provider.empId);
print("Session: ${provider.session}");
print(": $result.");
print("finish");
print("=============================");
if (result != null) {
_response = result;
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Something went wrong: $e";
}
_isLoading = false;
notifyListeners();
}
}
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:generp/Models/hrmModels/tourExpensesAddViewResponse.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/tourExpensesListResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class TourExpensesProvider extends ChangeNotifier {
tourExpensesListResponse? _response;
bool _isLoading = false;
String? _errorMessage;
tourExpensesListResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
tourExpensesAddViewResponse? _response2;
tourExpensesAddViewResponse? get response2 => _response2;
List<String> get daAmountList =>
_response2?.daAmount?.map((e) => e.toString()).toList() ?? [];
List<String> get tourTypeList => _response2?.tourType ?? [];
List<String> get travelTypeList => _response2?.travelType ?? [];
// Controllers for Add form
final TextEditingController fromDateField = TextEditingController();
final TextEditingController toDateField = TextEditingController();
final TextEditingController dateController = TextEditingController();
DateTime? _date;
DateTime? _fromDate;
DateTime? _toDate;
/// Format date (yyyy-MM-dd)
String _formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date);
}
/// Set single date
void setDate(DateTime newDate) {
_date = newDate;
dateController.text = _formatDate(newDate);
notifyListeners();
}
/// Fetch tour expenses list
Future<void> fetchTourExpenses(BuildContext context, String pageNumber) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.tourExpensesListAPI(
provider.empId,
provider.session,
pageNumber,
);
debugPrint('empId: ${provider.empId}, session: ${provider.session}');
if (result != null) {
_response = result;
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Error: $e";
}
_isLoading = false;
notifyListeners();
}
Future<void> fetchTourExpensesAddView(BuildContext context, String tourBillId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.tourExpensesAddViewAPI(
provider.empId,
provider.session,
tourBillId,
);
debugPrint('empId: ${provider.empId}, session: ${provider.session}, tourBillId: $tourBillId');
if (result != null) {
_response2 = result;
} else {
_errorMessage = "No data found!";
}
} catch (e) {
_errorMessage = "Error: $e";
}
_isLoading = false;
notifyListeners();
}
Future<bool> addTourBill({
required BuildContext context,
required String placeOfVisit,
required String daAmount,
required String tourType,
required String tourDate,
required List<Map<String, dynamic>> travelExpenses,
required List<Map<String, dynamic>> hotelExpenses,
required List<Map<String, dynamic>> otherExpenses,
List<File>? travelImages,
List<File>? hotelImages,
List<File>? otherImages,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
if ((homeProvider.session ?? "").isEmpty || (homeProvider.empId ?? "").isEmpty) {
_errorMessage = "Invalid session or employee ID";
_isLoading = false;
notifyListeners();
return false;
}
debugPrint("Submitting Tour Bill => "
"place: $placeOfVisit, da: $daAmount, type: $tourType, "
"date: $tourDate, travelExp: ${travelExpenses.length}, "
"hotelExp: ${hotelExpenses.length}, "
"otherExp: ${otherExpenses.length}, "
"travelImages: ${travelImages?.length}, "
"hotelImages: ${hotelImages?.length},"
"otherImages: ${otherImages?.length}");
final result = await ApiCalling.addTourBillAPI(
sessionId: homeProvider.session ?? "",
empId: homeProvider.empId ?? "",
placeOfVisit: placeOfVisit,
daAmount: daAmount,
tourType: tourType,
tourDate: tourDate,
travelExpenses: travelExpenses,
hotelExpenses: hotelExpenses,
otherExpenses: otherExpenses,
travelImages: travelImages,
hotelImages: hotelImages,
otherImages: otherImages,
);
if (result != null) {
debugPrint(" Tour Bill Added Successfully");
_isLoading = false;
notifyListeners();
return true;
} else {
_errorMessage = "Failed to add Tour Bill";
_isLoading = false;
notifyListeners();
return false;
}
} catch (e) {
_errorMessage = "Error: $e";
_isLoading = false;
notifyListeners();
return false;
}
}
/// Show Cupertino DatePicker for leave form
/// Show Cupertino DatePicker for leave form
Future<DateTime?> showDatePickerDialog(BuildContext context,
{bool isFromDate = true}) async {
DateTime currentDate = DateTime.now();
DateTime? pickedDate;
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 250,
padding: const EdgeInsets.only(top: 6.0),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: Column(
children: [
SizedBox(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: const Text("Cancel",
style: TextStyle(color: Colors.blue)),
onPressed: () => Navigator.pop(context),
),
CupertinoButton(
child: const Text("Done",
style: TextStyle(color: Colors.blue)),
onPressed: () {
pickedDate = currentDate;
if (isFromDate) {
fromDateField.text = _formatDate(pickedDate!);
} else {
toDateField.text = _formatDate(pickedDate!);
}
Navigator.pop(context);
},
),
],
),
),
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: currentDate,
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
currentDate = newDate;
},
),
),
],
),
),
),
);
return pickedDate;
}
}
...@@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart'; import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart';
import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/app_colors.dart';
import 'screens/notifierExports.dart'; import 'screens/notifierExports.dart';
import 'package:generp/Utils/SharedpreferencesService.dart'; import 'package:generp/Utils/SharedpreferencesService.dart';
...@@ -226,6 +227,12 @@ class MyApp extends StatelessWidget { ...@@ -226,6 +227,12 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => followUpUpdateProvider()), ChangeNotifierProvider(create: (_) => followUpUpdateProvider()),
ChangeNotifierProvider(create: (_) => Appointmentcalendarprovider()), ChangeNotifierProvider(create: (_) => Appointmentcalendarprovider()),
ChangeNotifierProvider(create: (_) => Addnewleadsandprospectsprovider()), ChangeNotifierProvider(create: (_) => Addnewleadsandprospectsprovider()),
ChangeNotifierProvider(create: (_) => Attendancelistprovider()),
ChangeNotifierProvider(create: (_) => AttendanceDetailsProvider()),
ChangeNotifierProvider(create: (_) => TourExpensesProvider()),
ChangeNotifierProvider(create: (_) => TourExpensesDetailsProvider()),
ChangeNotifierProvider(create: (_) => RewardListProvider()),
ChangeNotifierProvider(create: (_) => LeaveApplicationListProvider()),
], ],
child: Builder( child: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
......
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../Utils/app_colors.dart';
import '../Utils/dropdownTheme.dart';
class CommonFilter2 {
Dropdowntheme ddtheme = Dropdowntheme();
final List<String> filterItems = [
'Today',
'Yesterday',
'This Month',
'Past 7 days',
'Last Month',
'Custom',
];
final List<String> typeItems = [
"All",
"Check In",
"Check Out",
"Check In/Out",
];
String? selectedValue; // Date range value
DateTimeRange? selectedDateRange;
String? selectedType; // Type value
// Get DateTimeRange based on filter selection
DateTimeRange? getDateRange(String filter) {
DateTime now = DateTime.now();
switch (filter) {
case 'Today':
return DateTimeRange(
start: DateTime(now.year, now.month, now.day),
end: DateTime(now.year, now.month, now.day),
);
case 'Yesterday':
DateTime yesterday = now.subtract(const Duration(days: 1));
return DateTimeRange(
start: DateTime(yesterday.year, yesterday.month, yesterday.day),
end: DateTime(yesterday.year, yesterday.month, yesterday.day),
);
case 'This Month':
return DateTimeRange(
start: DateTime(now.year, now.month, 1),
end: DateTime(now.year, now.month + 1, 0),
);
case 'Past 7 days':
return DateTimeRange(
start: now.subtract(const Duration(days: 6)),
end: DateTime(now.year, now.month, now.day),
);
case 'Last Month':
return DateTimeRange(
start: DateTime(now.year, now.month - 1, 1),
end: DateTime(now.year, now.month, 0),
);
case 'Custom':
return null;
default:
return null;
}
}
// Format a single DateTime to string
String formatDate(DateTime date) {
return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
}
// Get formatted date range as a list of strings
List<String> getFormattedDateRange(DateTimeRange? dateRange) {
if (dateRange != null) {
return [
formatDate(dateRange.start),
formatDate(dateRange.end),
];
}
return [];
}
Future<Map<String, dynamic>?> showFilterBottomSheet(BuildContext context) async {
String? tempSelectedValue = selectedValue;
DateTimeRange? tempSelectedDateRange = selectedDateRange;
DateTime? tempStartDate;
DateTime? tempEndDate;
DateTime displayedMonth = DateTime.now();
String? tempSelectedType = selectedType ?? "All";
Widget buildCalendar(StateSetter setState) {
final firstDayOfMonth = DateTime(displayedMonth.year, displayedMonth.month, 1);
final lastDayOfMonth = DateTime(displayedMonth.year, displayedMonth.month + 1, 0);
final firstDayOfWeek = firstDayOfMonth.weekday;
final daysInMonth = lastDayOfMonth.day;
final daysBefore = (firstDayOfWeek - 1) % 7;
List<Widget> dayWidgets = [];
final weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
dayWidgets.addAll(weekdays.map((day) => Center(
child: Text(
day,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
)));
for (int i = 0; i < daysBefore; i++) {
dayWidgets.add(Container());
}
for (int day = 1; day <= daysInMonth; day++) {
final currentDate = DateTime(displayedMonth.year, displayedMonth.month, day);
bool isSelected = false;
bool isInRange = false;
bool isOutsideRange =
currentDate.isBefore(DateTime(2020)) || currentDate.isAfter(DateTime(2100));
if (tempStartDate != null && tempEndDate != null) {
isSelected = currentDate.isAtSameMomentAs(tempStartDate!) ||
currentDate.isAtSameMomentAs(tempEndDate!);
isInRange = currentDate.isAfter(tempStartDate!) &&
currentDate.isBefore(tempEndDate!) &&
!isSelected;
} else if (tempStartDate != null) {
isSelected = currentDate.isAtSameMomentAs(tempStartDate!);
}
dayWidgets.add(
GestureDetector(
onTap: isOutsideRange
? null
: () {
setState(() {
if (tempStartDate == null) {
tempStartDate = currentDate;
tempSelectedDateRange = null;
} else if (tempEndDate == null) {
if (currentDate.isBefore(tempStartDate!)) {
tempEndDate = tempStartDate;
tempStartDate = currentDate;
} else {
tempEndDate = currentDate;
}
tempSelectedDateRange =
DateTimeRange(start: tempStartDate!, end: tempEndDate!);
} else {
tempStartDate = currentDate;
tempEndDate = null;
tempSelectedDateRange = null;
}
});
},
child: Container(
margin: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: isSelected
? Colors.blue[600]
: isInRange
? Colors.blue[100]
: null,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'$day',
style: TextStyle(
color: isOutsideRange
? Colors.grey[400]
: isSelected
? Colors.white
: Colors.black,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
),
),
);
}
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: displayedMonth.isAfter(DateTime(2020))
? () {
setState(() {
displayedMonth =
DateTime(displayedMonth.year, displayedMonth.month - 1);
});
}
: null,
child: SvgPicture.asset("assets/svg/arrow_left.svg"),
),
Text(
'${_monthName(displayedMonth.month)} ${displayedMonth.year}',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
GestureDetector(
onTap: displayedMonth.isBefore(DateTime(2100))
? () {
setState(() {
displayedMonth =
DateTime(displayedMonth.year, displayedMonth.month + 1);
});
}
: null,
child: SvgPicture.asset("assets/svg/arrow_right_new.svg"),
),
],
),
SizedBox(
height: 280,
child: GridView.count(
crossAxisCount: 7,
childAspectRatio: 1.2,
children: dayWidgets,
physics: const NeverScrollableScrollPhysics(),
),
),
],
);
}
return await showModalBottomSheet<Map<String, dynamic>>(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Filter Attendance',
style: TextStyle(
color: AppColors.app_blue,
fontSize: 18,
fontFamily: "JakartaSemiBold",
),
),
const SizedBox(height: 20),
/// Type filter
const Text("Type",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<String>(
isExpanded: true,
value: tempSelectedType,
items: typeItems
.map((type) =>
DropdownMenuItem<String>(value: type, child: Text(type)))
.toList(),
onChanged: (value) {
setState(() {
tempSelectedType = value;
});
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
const SizedBox(height: 20),
/// Date range filter
const Text("Date Range",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<String>(
isExpanded: true,
hint: Text(
tempSelectedValue ?? 'Select Item',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
items: filterItems
.map((String item) =>
DropdownMenuItem<String>(
value: item, child: Text(
item,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
)
)
)
.toList(),
value: tempSelectedValue,
onChanged: (String? value) {
if (value == null) return;
setState(() {
tempSelectedValue = value;
if (value != 'Custom') {
tempSelectedDateRange = getDateRange(value);
tempStartDate = null;
tempEndDate = null;
} else {
tempSelectedDateRange = null;
tempStartDate = null;
tempEndDate = null;
}
});
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
if (tempSelectedValue == 'Custom') ...[
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: buildCalendar(setState),
),
if (tempSelectedDateRange != null)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(
'Selected: ${formatDate(tempSelectedDateRange!.start)} to ${formatDate(tempSelectedDateRange!.end)}',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
],
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel',
style: TextStyle(color: Colors.grey[600])),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
Navigator.pop(context, {
'type': tempSelectedType, // if you store type separately
'selectedValue': tempSelectedValue, // could be null
'dateRange': tempSelectedDateRange, // could be null
'formatted': tempSelectedDateRange != null
? getFormattedDateRange(tempSelectedDateRange)
: null,
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue[600],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Apply'),
),
],
),
],
),
),
),
);
},
);
},
);
}
String _monthName(int month) {
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
return months[month - 1];
}
}
...@@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:generp/Utils/commonWidgets.dart'; import 'package:generp/Utils/commonWidgets.dart';
import '../Utils/commonServices.dart'; import '../Utils/commonServices.dart';
import 'genTracker/ScanEnterGeneratorIDScreen.dart'; import 'genTracker/ScanEnterGeneratorIDScreen.dart';
import 'hrm/HrmDashboardScreen.dart';
import 'notifierExports.dart'; import 'notifierExports.dart';
import 'screensExports.dart'; import 'screensExports.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
...@@ -124,6 +125,8 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -124,6 +125,8 @@ class _MyHomePageState extends State<MyHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
switch (_source.keys.toList()[0]) { switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile: case ConnectivityResult.mobile:
connection = 'Online'; connection = 'Online';
...@@ -138,10 +141,10 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -138,10 +141,10 @@ class _MyHomePageState extends State<MyHomePage> {
return (connection == 'Online') return (connection == 'Online')
? Consumer2<HomescreenNotifier, ProfileNotifer>( ? Consumer2<HomescreenNotifier, ProfileNotifer>(
builder: (context, homescreen, profile, child) { builder: (context, homescreen, profile, child) {
final coreRequiredRoles = ["12", "540","433", "434", ]; final coreRequiredRoles = ["12", "540","433", "434", "430"];
final requiredRoles = ["430", "430", "431", "431"]; final requiredRoles = ["430", "430", "431", "431"];
final coreNames = ["CRM", "Orders","Service", "Gen Tracker", ]; final coreNames = ["CRM", "Orders","Service", "Gen Tracker","HRM" ];
final names = ["Attendance", "Finance", "ERP", "Whizzdom"]; final names = ["Attendance", "Finance", "ERP", "Whizzdom"];
final subtitles = [ final subtitles = [
"Check-in,Check-out", "Check-in,Check-out",
...@@ -154,6 +157,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -154,6 +157,7 @@ class _MyHomePageState extends State<MyHomePage> {
"assets/svg/home/home_order_ic.svg", "assets/svg/home/home_order_ic.svg",
"assets/svg/home/home_service_ic.svg", "assets/svg/home/home_service_ic.svg",
"assets/svg/home/home_gentracker_ic.svg", "assets/svg/home/home_gentracker_ic.svg",
"assets/svg/home/home_erp_ic.svg",
]; ];
final icons = [ final icons = [
...@@ -167,6 +171,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -167,6 +171,7 @@ class _MyHomePageState extends State<MyHomePage> {
"Orders, TPC, Dispatch", "Orders, TPC, Dispatch",
"Visits, P.C. Wallet", "Visits, P.C. Wallet",
"Generator Details", "Generator Details",
"Tour Bills, Live Attendance",
]; ];
final coreFilteredItems = <Map<String, String>>[]; final coreFilteredItems = <Map<String, String>>[];
final filteredItems = <Map<String, String>>[]; final filteredItems = <Map<String, String>>[];
...@@ -211,7 +216,8 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -211,7 +216,8 @@ class _MyHomePageState extends State<MyHomePage> {
toolbarHeight: 0, toolbarHeight: 0,
backgroundColor: Colors.white, backgroundColor: Colors.white,
), ),
body: Container( body: SingleChildScrollView(
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
...@@ -224,10 +230,9 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -224,10 +230,9 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( InkResponse(
flex: 4,
child: InkResponse(
onTap: () { onTap: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
_showProfileBottomSheet( _showProfileBottomSheet(
...@@ -406,10 +411,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -406,10 +411,7 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
), ),
), ),
), SizedBox(
Expanded(
flex: 13,
child: SizedBox(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
...@@ -1263,6 +1265,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1263,6 +1265,7 @@ class _MyHomePageState extends State<MyHomePage> {
child: GridView.builder( child: GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: coreFilteredItems.length, itemCount: coreFilteredItems.length,
physics: NeverScrollableScrollPhysics(),
gridDelegate: gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount( SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
...@@ -1332,6 +1335,19 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1332,6 +1335,19 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
), ),
); );
case "HRM":
res = await Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
HrmdashboardScreen(),
settings: RouteSettings(
name:
'CrmdashboardScreen',
),
),
);
default: default:
print("111"); print("111");
break; break;
...@@ -1406,7 +1422,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1406,7 +1422,7 @@ class _MyHomePageState extends State<MyHomePage> {
Expanded( Expanded(
flex: 1, flex: 1,
child: SvgPicture.asset( child: SvgPicture.asset(
filteredItems[ci]['icon'] ?? coreFilteredItems[ci]['icon'] ??
"-", "-",
), ),
), ),
...@@ -1423,7 +1439,6 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1423,7 +1439,6 @@ class _MyHomePageState extends State<MyHomePage> {
], ],
), ),
), ),
),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Container( child: Container(
...@@ -1655,6 +1670,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1655,6 +1670,7 @@ class _MyHomePageState extends State<MyHomePage> {
], ],
), ),
), ),
),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, // floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
// floatingActionButton: // floatingActionButton:
), ),
......
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonServices.dart';
import '../../Utils/commonWidgets.dart';
import '../../Utils/dropdownTheme.dart';
import '../../Notifiers/hrmProvider/leaveApplicationListProvider.dart';
class AddLeaveRequest extends StatefulWidget {
final String pageTitleName;
const AddLeaveRequest({super.key, required this.pageTitleName});
@override
State<AddLeaveRequest> createState() => _AddLeaveRequestState();
}
class _AddLeaveRequestState extends State<AddLeaveRequest> {
Dropdowntheme ddtheme = Dropdowntheme();
List<FocusNode> focusNodes = List.generate(6, (index) => FocusNode());
Map _source = {ConnectivityResult.mobile: true};
final MyConnectivity _connectivity = MyConnectivity.instance;
String? leaveType;
List<String> leaveTypes = ["Normal", "Medical"];
// Validation error messages
String? fromDateError;
String? fromTimeError;
String? toDateError;
String? toTimeError;
String? leaveTypeError;
String? reasonError;
@override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
setState(() => _source = source);
});
// Add listener to reason controller to clear error when user starts typing
final provider = Provider.of<LeaveApplicationListProvider>(context, listen: false);
provider.reasonController.addListener(() {
if (reasonError != null && provider.reasonController.text.isNotEmpty) {
setState(() => reasonError = null);
}
});
}
@override
void dispose() {
focusNodes.map((e) => e.dispose());
_connectivity.disposeStream();
super.dispose();
}
Future<bool> _onBackPressed(BuildContext context) async {
Navigator.pop(context, true);
return true;
}
// Function to validate all fields
bool validateForm(LeaveApplicationListProvider provider) {
String? newFromDateError = provider.fromDateField.text.isEmpty ? "From date is required" : null;
String? newFromTimeError = provider.fromTimeField.text.isEmpty ? "From time is required" : null;
String? newToDateError = provider.toDateField.text.isEmpty ? "To date is required" : null;
String? newToTimeError = provider.toTimeField.text.isEmpty ? "To time is required" : null;
String? newLeaveTypeError = leaveType == null ? "Leave type is required" : null;
String? newReasonError = provider.reasonController.text.isEmpty ? "Reason is required" : null;
// Only update if there are actual changes to avoid unnecessary rebuilds
if (fromDateError != newFromDateError ||
fromTimeError != newFromTimeError ||
toDateError != newToDateError ||
toTimeError != newToTimeError ||
leaveTypeError != newLeaveTypeError ||
reasonError != newReasonError) {
setState(() {
fromDateError = newFromDateError;
fromTimeError = newFromTimeError;
toDateError = newToDateError;
toTimeError = newToTimeError;
leaveTypeError = newLeaveTypeError;
reasonError = newReasonError;
});
}
return newFromDateError == null &&
newFromTimeError == null &&
newToDateError == null &&
newToTimeError == null &&
newLeaveTypeError == null &&
newReasonError == null;
}
@override
Widget build(BuildContext context) {
final leaveProvider = Provider.of<LeaveApplicationListProvider>(context);
switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile:
case ConnectivityResult.wifi:
connection = 'Online';
break;
case ConnectivityResult.none:
default:
connection = 'Offline';
}
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => _onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context, leaveProvider)),
)
: _scaffold(context, leaveProvider)
: NoNetwork(context);
}
Widget _scaffold(BuildContext context, LeaveApplicationListProvider provider) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// From Date
TextWidget(context, "From Date"),
GestureDetector(
onTap: () {
provider.showDatePickerDialog(context, isFromDate: true);
if (fromDateError != null) {
setState(() => fromDateError = null);
}
},
child: textFieldNew(context, provider.fromDateField,
"Select Date", enabled: false),
),
errorWidget(context, fromDateError),
/// From Time
TextWidget(context, "From Time"),
GestureDetector(
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
provider.fromTimeField.text = picked.format(context);
if (fromTimeError != null) {
setState(() => fromTimeError = null);
}
}
},
child: textFieldNew(context, provider.fromTimeField,
"Select Time", enabled: false),
),
errorWidget(context, fromTimeError),
/// To Date
TextWidget(context, "To Date"),
GestureDetector(
onTap: () {
provider.showDatePickerDialog(context, isFromDate: false);
if (toDateError != null) {
setState(() => toDateError = null);
}
},
child: textFieldNew(context, provider.toDateField, "Select Date",
enabled: false),
),
errorWidget(context, toDateError),
/// To Time
TextWidget(context, "To Time"),
GestureDetector(
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
provider.toTimeField.text = picked.format(context);
if (toTimeError != null) {
setState(() => toTimeError = null);
}
}
},
child: textFieldNew(context, provider.toTimeField, "Select Time",
enabled: false),
),
errorWidget(context, toTimeError),
/// Leave Type
TextWidget(context, "Leave Type"),
Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
hint: const Text("Select Leave Type",
style: TextStyle(fontSize: 14, color: Colors.grey)),
value: leaveType,
items: leaveTypes
.map((e) =>
DropdownMenuItem(value: e, child: Text(e)))
.toList(),
onChanged: (val) {
setState(() {
leaveType = val;
if (leaveTypeError != null) {
leaveTypeError = null;
}
});
},
),
),
),
errorWidget(context, leaveTypeError),
/// Reason
TextWidget(context, "Reason"),
textFieldNew(context, provider.reasonController, "Enter Reason",
maxLines: 2),
errorWidget(context, reasonError),
const SizedBox(height: 70),
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: InkResponse(
onTap: () {
if (validateForm(provider)) {
provider.addLeaveRequest(
context,
fromDate: provider.fromDateField.text,
fromTime: provider.fromTimeField.text,
toDate: provider.toDateField.text,
toTime: provider.toTimeField.text,
leaveType: leaveType!,
reason: provider.reasonController.text,
);
// Reset after submit
setState(() {
provider.fromDateField.clear();
provider.fromTimeField.clear();
provider.toDateField.clear();
provider.toTimeField.clear();
provider.reasonController.clear();
leaveType = null;
fromDateError = null;
fromTimeError = null;
toDateError = null;
toTimeError = null;
leaveTypeError = null;
reasonError = null;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Leave request submitted successfully!"),
backgroundColor: Colors.black87,
),
);
}
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: provider.isSubmitting
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white),
),
),
),
);
}
Widget textFieldNew(
BuildContext context,
TextEditingController controller,
String hint, {
bool enabled = true,
int maxLines = 1,
}) {
return Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: controller,
enabled: enabled,
maxLines: maxLines,
style: TextStyle(
color: Colors.black, // Entered text color
fontSize: 14, // Optional: adjust font size
),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
color: Colors.grey.shade500, // Customize this color
fontSize: 14, // Optional: tweak font size
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
);
}
Widget errorText(String msg) {
return Padding(
padding: const EdgeInsets.only(bottom: 12, left: 4),
child: Text(
msg,
style: const TextStyle(color: Colors.red, fontSize: 13),
),
);
}
bool _validateForm(LeaveApplicationListProvider provider) {
bool isValid = true;
setState(() {
fromDateError =
provider.fromDateField.text.isEmpty ? "From date is required" : null;
fromTimeError =
provider.fromTimeField.text.isEmpty ? "From time is required" : null;
toDateError =
provider.toDateField.text.isEmpty ? "To date is required" : null;
toTimeError =
provider.toTimeField.text.isEmpty ? "To time is required" : null;
leaveTypeError = leaveType == null ? "Please select leave type" : null;
reasonError = provider.reasonController.text.isEmpty
? "Reason is required"
: null;
if (fromDateError != null ||
fromTimeError != null ||
toDateError != null ||
toTimeError != null ||
leaveTypeError != null ||
reasonError != null) {
isValid = false;
}
});
return isValid;
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/hrmProvider/attendanceListProvider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
class AddLiveAttendanceScreen extends StatefulWidget {
const AddLiveAttendanceScreen({Key? key}) : super(key: key);
@override
State<AddLiveAttendanceScreen> createState() =>
_AddLiveAttendanceScreenState();
}
class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
String? typeError;
String? locationError;
String? proofError;
String? selectedType;
Dropdowntheme ddtheme = Dropdowntheme();
final TextEditingController locationController = TextEditingController();
final TextEditingController descriptionController = TextEditingController();
final List<String> types = ["Check In", "Check Out"];
final ImagePicker picker = ImagePicker();
XFile? proofFile;
bool isSubmitting = false;
String get locationHeading =>
selectedType == null ? "Location" : "$selectedType Location";
String get descriptionHeading =>
selectedType == null ? "Description" : "$selectedType Description";
String get proofButtonText =>
selectedType == null ? "Attach Proof" : "Attach $selectedType Proof";
bool get isSubmitEnabled =>
selectedType != null &&
locationController.text.trim().isNotEmpty &&
proofFile != null;
@override
void initState() {
super.initState();
_autoFetchLocation();
}
Future<void> _autoFetchLocation() async {
String loc = await getCurrentLocation();
setState(() {
locationController.text = loc;
});
}
Future<String> getCurrentLocation() async {
try {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return "Location permission denied";
}
}
if (permission == LocationPermission.deniedForever) {
return "Location permissions permanently denied";
}
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
List<Placemark> placemarks =
await placemarkFromCoordinates(position.latitude, position.longitude);
if (placemarks.isNotEmpty) {
Placemark place = placemarks.first;
return "${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.country}";
} else {
return "${position.latitude}, ${position.longitude}";
}
} catch (e) {
return "Error: $e";
}
}
void _showPicker(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text('Camera'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.camera);
if (image != null) {
setState(() => proofFile = image);
}
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Gallery'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() => proofFile = image);
}
},
),
],
),
);
},
);
}
void submitAttendance(BuildContext context) async {
setState(() {
typeError = null;
locationError = null;
proofError = null;
});
bool hasError = false;
if (selectedType == null) {
typeError = "Please select a type";
hasError = true;
}
if (locationController.text.trim().isEmpty) {
locationError = "Please enter a location";
hasError = true;
}
if (proofFile == null) {
proofError = "Please attach proof";
hasError = true;
}
if (hasError) {
setState(() {});
return;
}
setState(() => isSubmitting = true);
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
await provider.addAttendanceRequest(
context,
process: "Live",
type: selectedType ?? "",
loc: locationController.text,
checkDate: DateTime.now().toString().split(" ").first,
checkInTime:
selectedType == "Check In" ? TimeOfDay.now().format(context) : null,
checkInLoc: selectedType == "Check In" ? locationController.text : null,
checkInProof: selectedType == "Check In" ? File(proofFile!.path) : null,
checkOutTime:
selectedType == "Check Out" ? TimeOfDay.now().format(context) : null,
checkOutLoc: selectedType == "Check Out" ? locationController.text : null,
checkOutProof:
selectedType == "Check Out" ? File(proofFile!.path) : null,
note: descriptionController.text,
);
setState(() {
isSubmitting = false;
selectedType = null;
locationController.clear();
descriptionController.clear();
proofFile = null;
});
_showSnack(context, "Attendance Submitted Successfully!");
_autoFetchLocation();
}
void _showSnack(BuildContext context, String msg) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(msg), backgroundColor: Colors.black87),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
Text(
"Add Live Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text(
"Select Type",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400),
),
value: selectedType,
items: types
.map((e) => DropdownMenuItem<String>(
value: e,
child: Text(
e,
style: const TextStyle(
fontSize: 14, fontFamily: "JakartaMedium"),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
iconStyleData: ddtheme.iconStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
),
if (typeError != null) ...[
const SizedBox(height: 4),
Text(typeError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16),
/// Location
Text(locationHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: locationController,
decoration: _inputDecoration("Enter location"),
),
if (locationError != null) ...[
const SizedBox(height: 4),
Text(locationError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16),
/// Description
Text(descriptionHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 20),
/// Attach Proof
InkResponse(
onTap: () => _showPicker(context),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: Colors.blue.shade50,
border: Border.all(color: Colors.blue.shade200),
borderRadius: BorderRadius.circular(14),
),
child: Center(
child: Text(
proofButtonText,
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500,
),
),
),
),
),
if (proofError != null) ...[
const SizedBox(height: 4),
Text(proofError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text("Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: "JakartaMedium", fontSize: 14))),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
/// Submit Button
InkResponse(
onTap:
isSubmitEnabled && !isSubmitting ? () => submitAttendance(context) : null,
child: Container(
height: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSubmitEnabled
? AppColors.app_blue
: Colors.grey.shade400,
borderRadius: BorderRadius.circular(12),
),
child: isSubmitting
? const CircularProgressIndicator(
color: Colors.white, strokeWidth: 2)
: const Text(
"Submit",
style: TextStyle(
fontSize: 16,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
],
),
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400,
color: Colors.grey,
),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColors.app_blue),
),
);
}
}
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/cupertino.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter_svg/svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:geolocator/geolocator.dart';
import 'package:geocoding/geocoding.dart';
import '../../Models/ordersModels/commonResponse.dart';
import '../../Notifiers/hrmProvider/attendanceListProvider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonServices.dart';
import '../../Utils/commonWidgets.dart';
import '../../Utils/dropdownTheme.dart';
class AddManualAttendanceScreen extends StatefulWidget {
const AddManualAttendanceScreen({super.key});
@override
State<AddManualAttendanceScreen> createState() =>
_AddManualAttendanceScreenState();
}
class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
Dropdowntheme ddtheme = Dropdowntheme();
final ImagePicker picker = ImagePicker();
// Connectivity
Map _source = {ConnectivityResult.mobile: true};
final MyConnectivity _connectivity = MyConnectivity.instance;
String connection = "Online";
// Controllers
final checkInTime = TextEditingController();
final checkInLocation = TextEditingController();
final checkInDescription = TextEditingController();
XFile? checkInProof;
final checkOutTime = TextEditingController();
final checkOutLocation = TextEditingController();
final checkOutDescription = TextEditingController();
XFile? checkOutProof;
String? selectedType;
final List<String> types = ["Check In", "Check Out", "Check In/Out"];
// Errors
String? dateError, typeError;
String? checkInTimeError, checkInLocError, checkInDescError, checkInProofError;
String? checkOutTimeError, checkOutLocError, checkOutDescError, checkOutProofError;
// In your Attendancelistprovider class
CommonResponse? get addResponse => addResponse;
String? get errorMessage => errorMessage;
@override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((src) {
setState(() => _source = src);
});
WidgetsBinding.instance.addPostFrameCallback((_) {
_fetchInitialLocation();
});
}
@override
void dispose() {
_connectivity.disposeStream();
super.dispose();
}
Future<void> _fetchInitialLocation() async {
String loc = await getCurrentLocation();
setState(() {
checkInLocation.text = loc;
checkOutLocation.text = loc;
});
}
Future<String> getCurrentLocation() async {
try {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) return "Permission denied";
}
if (permission == LocationPermission.deniedForever) {
return "Permission permanently denied";
}
Position pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
List<Placemark> placemarks =
await placemarkFromCoordinates(pos.latitude, pos.longitude);
if (placemarks.isNotEmpty) {
Placemark p = placemarks.first;
return "${p.locality}, ${p.administrativeArea}, ${p.country}";
}
return "${pos.latitude}, ${pos.longitude}";
} catch (e) {
return "Error: $e";
}
}
Future<void> _pickTime(TextEditingController controller) async {
final TimeOfDay? picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picked != null) controller.text = picked.format(context);
}
Future<void> _pickFile(bool isCheckIn) async {
showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
showDragHandle: true,
backgroundColor: Colors.white,
context: context,
builder: (_) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text("Capture photo from camera"),
onTap: () async {
final XFile? file =
await picker.pickImage(source: ImageSource.camera);
if (file != null) {
setState(() {
if (isCheckIn) checkInProof = file;
else checkOutProof = file;
});
}
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text("Select photo from gallery"),
onTap: () async {
final XFile? file =
await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
setState(() {
if (isCheckIn) checkInProof = file;
else checkOutProof = file;
});
}
Navigator.pop(context);
},
),
],
),
);
},
);
}
void _submitForm(BuildContext context) async {
// reset errors first
dateError = null;
typeError = null;
checkInTimeError = checkInLocError = checkInDescError = checkInProofError = null;
checkOutTimeError = checkOutLocError = checkOutDescError = checkOutProofError = null;
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
// --- Date Validation ---
if (provider.dateController.text.isEmpty) {
dateError = "Please select a date";
} else {
try {
final enteredDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text);
provider.setSelectedDate(enteredDate);
if (!provider.isDateValid()) {
dateError = "Date must be today or yesterday";
}
} catch (e) {
dateError = "Invalid date format (use dd MMM yyyy)";
}
}
// --- Type Validation ---
if (selectedType == null) {
typeError = "Please select type";
}
// --- Check In Validations ---
if (selectedType == "Check In" || selectedType == "Check In/Out") {
if (checkInTime.text.isEmpty) checkInTimeError = "Please select check-in time";
if (checkInLocation.text.isEmpty) checkInLocError = "Please enter check-in location";
if (checkInDescription.text.isEmpty) checkInDescError = "Please enter description";
if (checkInProof == null) checkInProofError = "Please attach check-in proof";
}
// --- Check Out Validations ---
if (selectedType == "Check Out" || selectedType == "Check In/Out") {
if (checkOutTime.text.isEmpty) checkOutTimeError = "Please select check-out time";
if (checkOutLocation.text.isEmpty) checkOutLocError = "Please enter check-out location";
if (checkOutDescription.text.isEmpty) checkOutDescError = "Please enter description";
if (checkOutProof == null) checkOutProofError = "Please attach check-out proof";
}
// --- Stop if any error ---
if ([
dateError,
typeError,
checkInTimeError,
checkInLocError,
checkInDescError,
checkInProofError,
checkOutTimeError,
checkOutLocError,
checkOutDescError,
checkOutProofError
].any((e) => e != null)) {
setState(() {});
return;
}
// --- Build data according to type ---
String? finalCheckInTime;
String? finalCheckInLoc;
File? finalCheckInProof;
String? finalCheckOutTime;
String? finalCheckOutLoc;
File? finalCheckOutProof;
String? finalNote;
if (selectedType == "Check In") {
finalCheckInTime = checkInTime.text;
finalCheckInLoc = checkInLocation.text;
finalCheckInProof = File(checkInProof!.path);
finalNote = checkInDescription.text;
} else if (selectedType == "Check Out") {
finalCheckOutTime = checkOutTime.text;
finalCheckOutLoc = checkOutLocation.text;
finalCheckOutProof = File(checkOutProof!.path);
finalNote = checkOutDescription.text;
} else if (selectedType == "Check In/Out") {
finalCheckInTime = checkInTime.text;
finalCheckInLoc = checkInLocation.text;
finalCheckInProof = File(checkInProof!.path);
finalCheckOutTime = checkOutTime.text;
finalCheckOutLoc = checkOutLocation.text;
finalCheckOutProof = File(checkOutProof!.path);
finalNote = "${checkInDescription.text} / ${checkOutDescription.text}";
}
// --- Submit to provider ---
await provider.addAttendanceRequest(
context,
process: "Manual",
type: selectedType!,
loc: selectedType == "Check In"
? checkInLocation.text
: selectedType == "Check Out"
? checkOutLocation.text
: "${checkInLocation.text}, ${checkOutLocation.text}",
checkDate: provider.dateController.text,
checkInTime: finalCheckInTime,
checkInLoc: finalCheckInLoc,
checkInProof: finalCheckInProof,
checkOutTime: finalCheckOutTime,
checkOutLoc: finalCheckOutLoc,
checkOutProof: finalCheckOutProof,
note: finalNote,
);
// Check the response from provider
if (provider.addResponse != null && provider.addResponse!.error == "0") {
// Success case
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(provider.addResponse!.message ?? "Attendance Submitted Successfully")),
);
// --- Reset fields ---
setState(() {
selectedType = null;
provider.dateController.clear();
checkInTime.clear();
checkInLocation.clear();
checkInDescription.clear();
checkInProof = null;
checkOutTime.clear();
checkOutLocation.clear();
checkOutDescription.clear();
checkOutProof = null;
});
_fetchInitialLocation();
} else {
// Error case - show appropriate message
String errorMessage = provider.errorMessage ?? "Failed to submit attendance";
// Handle specific server error for Check Out without Check In
if (errorMessage.contains("Check In is not Available")) {
errorMessage = "Cannot submit Check Out without a Check In record for this date";
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
);
}
}
Future<bool> _onBackPressed(BuildContext context) async {
Navigator.pop(context, true);
return true;
}
@override
Widget build(BuildContext context) {
switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile:
case ConnectivityResult.wifi:
connection = 'Online';
break;
default:
connection = 'Offline';
}
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => _onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
),
)
: _scaffold(context)
: NoNetwork(context);
}
Widget _scaffold(BuildContext context) {
return Consumer<Attendancelistprovider>(
builder: (context, provider, child) {
return Scaffold(
backgroundColor: Colors.white,
appBar: appbar2New(
context,
"Add Manual Attendance",
() {},
SizedBox.shrink(),
0xFFFFFFFF,
),
body: Scrollbar(
child: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// TextWidget(context, "Date"),
GestureDetector(
onTap: () => provider.showDatePickerDialog(context),
child: AbsorbPointer(
child: textControllerWidget(
context,
provider.dateController,
"Date",
"Select Date",
(v) {},
TextInputType.text,
false,
null,
null,
null,
TextInputAction.next,
),
),
),
errorWidget(context, dateError),
TextWidget(context, "Type"),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text("Select Type"),
items: types
.map((e) => DropdownMenuItem(
value: e,
child: Text(e),
))
.toList(),
value: selectedType,
onChanged: (val) {
setState(() => selectedType = val);
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
errorWidget(context, typeError),
if (selectedType == "Check In" || selectedType == "Check In/Out")
_buildSection("Check In"),
if (selectedType == "Check Out" || selectedType == "Check In/Out")
_buildSection("Check Out"),
SizedBox(height: 80),
],
),
),
),
bottomNavigationBar: InkResponse(
onTap: provider.isSubmitting ? null : () => _submitForm(context),
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: provider.isSubmitting
? CircularProgressIndicator.adaptive(
valueColor: AlwaysStoppedAnimation(AppColors.white),
)
: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
);
},
);
}
Widget _buildSection(String title) {
final isCheckIn = title == "Check In";
final timeCtrl = isCheckIn ? checkInTime : checkOutTime;
final locCtrl = isCheckIn ? checkInLocation : checkOutLocation;
final descCtrl = isCheckIn ? checkInDescription : checkOutDescription;
final proofFile = isCheckIn ? checkInProof : checkOutProof;
final proofError = isCheckIn ? checkInProofError : checkOutProofError;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// TextWidget(context, "$title Time"),
GestureDetector(
onTap: () => _pickTime(timeCtrl), // ⏰ open time picker
child: AbsorbPointer(
child: textControllerWidget(
context,
timeCtrl,
"$title Time",
"Select Time",
(v) {},
TextInputType.text,
false,
null,
null,
null,
TextInputAction.next,
),
),
),
errorWidget(context, isCheckIn ? checkInTimeError : checkOutTimeError),
textControllerWidget(
context,
locCtrl,
"$title Location",
"Enter Location",
(v) {},
TextInputType.text,
false,
null,
null,
null,
TextInputAction.next,
),
errorWidget(context, isCheckIn ? checkInLocError : checkOutLocError),
textControllerWidget(
context,
descCtrl,
"$title Description",
"Enter Description",
(v) {},
TextInputType.text,
false,
null,
null,
null,
TextInputAction.done,
),
errorWidget(context, isCheckIn ? checkInDescError : checkOutDescError),
InkResponse(
onTap: () => _pickFile(isCheckIn),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 45,
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0xFFE6F6FF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.app_blue, width: 0.5),
),
child: Center(
child: Text(
"Attach $title Proof",
style: TextStyle(
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
),
),
),
if (proofFile != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
proofFile.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.red, size: 18),
onPressed: () {
setState(() {
if (isCheckIn) checkInProof = null;
else checkOutProof = null;
});
},
)
],
),
errorWidget(context, proofError),
],
);
}
}
This diff is collapsed.
This diff is collapsed.
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/Utils/GlobalConstants.dart';
import 'package:generp/screens/hrm/AddManualAttendance.dart';
import 'package:generp/screens/hrm/AttendanceRequestDetail.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/hrmProvider/attendanceListProvider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonWidgets.dart';
import '../CommonFilter2.dart';
import '../commonDateRangeFilter.dart';
import 'AddLiveAttendance.dart';
class Attendancelist extends StatefulWidget {
const Attendancelist({super.key});
@override
State<Attendancelist> createState() => _AttendancelistState();
}
class _AttendancelistState extends State<Attendancelist> {
// @override
// void initState() {
// super.initState();
// WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// final provider = Provider.of<Attendancelistprovider>(context, listen: false);
// provider.fetchAttendanceRequests(context);
// });
// }
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) {
final provider = Attendancelistprovider();
Future.microtask(() {
provider.fetchAttendanceRequests(context);
});
return provider;
},
builder: (context, child) {
return Consumer<Attendancelistprovider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
Text(
"Attendance List",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
],
),
actions: [
InkResponse(
onTap: () async {
final result = await CommonFilter2().showFilterBottomSheet(context);
if (result != null) {
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
provider.updateFiltersFromSheet(
context,
type: result['type'] ?? "All",
selectedValue: result['selectedValue'] ?? "This Month",
customRange: result['dateRange'],
);
}
},
child: SvgPicture.asset(
"assets/svg/filter_ic.svg",
height: 25,
),
),
const SizedBox(width: 20),
],
),
backgroundColor: const Color(0xFFF6F6F8),
body: Column(
children: [
/// Filter chips - show active filters
// if (provider.selectedType != "All" || provider.selectedDateRange != "This Month")
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
// color: Colors.white,
// child: Wrap(
// spacing: 8,
// children: [
// if (provider.selectedType != "All")
// Chip(
// label: Text('Type: ${provider.selectedType}'),
// onDeleted: () {
// provider.setTypeFilter(context, "All");
// },
// ),
// if (provider.selectedDateRange != "This Month")
// Chip(
// label: Text('Date: ${provider.selectedDateRange}'),
// onDeleted: () {
// provider.setDateRangeFilter(context, "This Month");
// },
// ),
// ],
// ),
// ),
/// Attendance list
Expanded(
child: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response?.requestList == null ||
provider.response!.requestList!.isEmpty) {
return const Center(
child: Text(
"No attendance records found",
style: TextStyle(fontSize: 16, color: Colors.grey),
),
);
}
final list = provider.response!.requestList!;
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
/// navigation flow
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceRequestDetailScreen(
attendanceListId: item.id,
),
),
);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Avatar Circle
Container(
height: 48,
width: 50,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: _getAvatarColor(item.status),
shape: BoxShape.circle,
),
child: Center(
child: Text(
getText(item.status),
style: TextStyle(
color: _getTextColor(item.status),
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 10),
/// Middle Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.type ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
Text(
item.date ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
],
),
),
/// Right Status (Live / Manual)
Text(
item.attendanceType ?? "-",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: (item.attendanceType ?? "").toLowerCase() == "live"
? Colors.green
: Colors.orange,
),
),
],
),
),
);
},
);
},
),
)
],
),
bottomNavigationBar: Container(
alignment: Alignment.bottomCenter,
height: 54,
decoration: const BoxDecoration(color: Colors.white),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddLiveAttendanceScreen(),
settings: const RouteSettings(
name: 'AddLiveAttendanceScreen',
),
),
).then((_) {
provider.fetchAttendanceRequests(context);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/hrm/live.svg"),
const SizedBox(width: 10),
Text("Live Request",
style: TextStyle(color: AppColors.semi_black)),
],
),
),
),
const SizedBox(width: 10),
SvgPicture.asset("assets/svg/crm/vertical_line_ic.svg"),
const SizedBox(width: 10),
Expanded(
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddManualAttendanceScreen(),
settings: const RouteSettings(
name: 'AddManualAttendanceScreen'),
),
).then((_) {
provider.fetchAttendanceRequests(context);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/hrm/manual.svg"),
const SizedBox(width: 10),
Text("Manual Request",
style: TextStyle(color: AppColors.semi_black)),
],
),
),
),
],
),
),
);
},
);
},
)
);
}
/// Avatar color generator
Color _getAvatarColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'Requested':
return AppColors.requested_bg_color;
case 'Level 1 Approved':
return AppColors.approved_bg_color;
case 'Level 1 Rejected':
return AppColors.rejected_bg_color;
case 'Level 2 Approved':
return AppColors.approved_bg_color;
case 'Level 2 Rejected':
return AppColors.rejected_bg_color;
case 'Updated':
return AppColors.processed_bg_color;
case 'Payment Rejected':
return AppColors.rejected_bg_color;
}
return color;
}
Color _getTextColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'Requested':
return AppColors.requested_text_color;
case 'Level 1 Approved':
return AppColors.approved_text_color;
case 'Level 1 Rejected':
return AppColors.rejected_text_color;
case 'Level 2 Approved':
return AppColors.approved_text_color;
case 'Level 2 Rejected':
return AppColors.rejected_text_color;
case 'Updated':
return AppColors.processed_text_color;
}
return color;
}
getText(value) {
switch (value) {
case 'Requested':
return "R";
case 'Level 1 Approved':
return "L1A";
case 'Level 1 Rejected':
return "L1R";
case 'Level 2 Approved':
return "L2A";
case 'Level 2 Rejected':
return "L2R";
case 'Updated':
return "U";
default:
return "Requested";
}
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.