Commit f6fbe101 authored by Mohit Kumar's avatar Mohit Kumar
Browse files

AttendanceList

RewardList
TourExpenses
Implementation
parent 6d1deaf2
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_319_5695)">
<path d="M1.07683 7.38789C1.08816 7.31523 1.10027 7.24102 1.11238 7.16367L1.11706 7.13555C1.26472 6.20742 1.75886 5.28945 2.47917 4.53672C3.19949 3.78398 4.11238 3.23789 5.04988 3.04297C5.2741 2.99648 5.52136 2.95078 5.78738 2.90703C5.79805 2.70339 5.80925 2.49987 5.82097 2.29648C5.85105 1.78125 6.26355 1.31992 6.73738 1.27891C7.21121 1.23789 7.57956 1.63633 7.5616 2.16055C7.55535 2.33372 7.54923 2.50664 7.54324 2.6793C8.30066 2.60469 9.11355 2.55352 9.92605 2.53281C9.92605 2.37656 9.92748 2.22148 9.93035 2.06758C9.93659 1.53867 10.3268 1.10156 10.8014 1.10156C11.276 1.10156 11.6671 1.53945 11.6729 2.06758C11.6745 2.22383 11.6759 2.37891 11.6772 2.53281C12.4874 2.55352 13.2968 2.6043 14.0514 2.67852C14.0452 2.50586 14.0391 2.33307 14.0331 2.16016C14.0151 1.63555 14.3846 1.23594 14.8573 1.27813C15.33 1.32031 15.7436 1.78047 15.7737 2.2957C15.7854 2.49883 15.7966 2.70208 15.8073 2.90547C16.0768 2.94961 16.3276 2.99609 16.5542 3.04297C17.4917 3.23828 18.4034 3.78516 19.1249 4.53672C19.8464 5.28828 20.3393 6.20742 20.487 7.13555L20.4917 7.16406C20.5042 7.24219 20.5159 7.31523 20.5272 7.38789C14.0526 6.90977 7.55148 6.90977 1.07683 7.38789ZM20.8011 11.216C20.7686 13.5695 20.6038 14.341 20.4128 15.4801C20.4128 15.4898 20.4096 15.4992 20.4077 15.509C20.2432 16.4418 19.737 17.3734 19.0147 18.1445C18.2925 18.9156 17.3874 19.4797 16.4635 19.6895C15.1038 20.0051 12.8843 20.2672 10.8022 20.2582C8.72019 20.2672 6.50027 20.0051 5.1405 19.6895C4.21589 19.4801 3.31277 18.9137 2.58933 18.1449C1.86589 17.3762 1.36081 16.4422 1.19636 15.5094C1.19636 15.5 1.19285 15.4902 1.19128 15.4809C1.00027 14.341 0.835814 13.5707 0.803002 11.2164C0.794799 10.0418 0.838158 9.31328 0.893236 8.73633C7.48464 8.44414 14.1194 8.44414 20.7108 8.73633C20.7663 9.31289 20.8093 10.0418 20.8011 11.2164V11.216ZM7.19324 13.7902C7.1823 12.9789 6.54558 12.3148 5.76589 12.3027C5.58217 12.2984 5.39949 12.3316 5.22902 12.4002C5.05856 12.4689 4.9039 12.5716 4.7745 12.7021C4.6451 12.8326 4.54368 12.9882 4.47645 13.1592C4.40921 13.3302 4.37757 13.5132 4.38347 13.6969C4.40535 14.4781 5.05535 15.1574 5.8241 15.1977C6.59285 15.2379 7.20378 14.6012 7.19324 13.7902ZM12.2647 13.8266C12.2702 13.0082 11.6397 12.3477 10.8585 12.3473C10.0772 12.3469 9.44753 13.009 9.45222 13.8273C9.45691 14.6457 10.0882 15.3203 10.8585 15.3199C11.6288 15.3195 12.2589 14.6445 12.2647 13.8266ZM17.4155 13.6879C17.4214 13.5045 17.3898 13.3219 17.3226 13.1512C17.2554 12.9805 17.154 12.8253 17.0246 12.6952C16.8953 12.5651 16.7407 12.4627 16.5704 12.3945C16.4001 12.3263 16.2177 12.2936 16.0343 12.2984C15.253 12.3109 14.6178 12.9742 14.6061 13.7828C14.5944 14.5914 15.2046 15.2281 15.9733 15.1855C16.7421 15.143 17.3936 14.4691 17.4155 13.6879Z" fill="url(#paint0_linear_319_5695)"/>
</g>
<defs>
<linearGradient id="paint0_linear_319_5695" x1="12.1655" y1="23.2878" x2="-2.4722" y2="3.87753" gradientUnits="userSpaceOnUse">
<stop stop-color="#498D1E"/>
<stop offset="1" stop-color="#81C320"/>
</linearGradient>
<clipPath id="clip0_319_5695">
<rect width="20" height="20" fill="white" transform="translate(0.77832 0.679688)"/>
</clipPath>
</defs>
</svg>
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_319_5711)">
<path d="M22.595 9.39164C22.3853 7.75582 20.9282 6.3718 19.2232 6.14492C16.2184 5.84758 11.7789 5.82266 11.778 5.8334C9.85776 5.81363 5.36237 5.99668 4.33284 6.14492C3.29041 6.27941 2.34811 6.85434 1.71733 7.64582V7.64668C1.31471 8.15371 1.04143 8.75012 0.961078 9.39164C0.838617 10.3804 0.742367 11.7317 0.791351 13.5798C0.843343 15.4283 1.01307 16.7823 1.18666 17.7744C1.4853 19.4163 2.96256 20.8583 4.60655 21.1346C5.60256 21.3245 9.9308 21.5883 11.7785 21.5548C13.6261 21.5888 17.9544 21.3249 18.9504 21.1346C20.5944 20.8583 22.0716 19.4163 22.3703 17.7744C22.5434 16.7818 22.7131 15.4279 22.7656 13.5798C22.8146 11.7313 22.7179 10.3804 22.5958 9.39164H22.595ZM18.9388 15.2182C18.0974 15.2182 17.4151 14.5363 17.4151 13.6945C17.4151 12.8528 18.097 12.1709 18.9388 12.1709C19.7805 12.1709 20.4624 12.8528 20.4624 13.6945C20.4624 14.5363 19.7805 15.2182 18.9388 15.2182ZM4.2976 5.06555C5.42381 4.88852 10.0451 4.68914 11.7776 4.71492C13.2678 4.70074 16.8939 4.82664 18.5967 4.99465C18.0532 3.45809 16.6683 2.23477 15.0914 2.03281C13.9823 1.89445 12.469 1.78102 10.3996 1.80895C8.33022 1.83988 6.81428 1.99629 5.70182 2.16387C3.86448 2.45348 2.22307 4.06996 1.90768 5.92063C1.89994 5.96703 1.89221 6.01988 1.88362 6.07746C2.58787 5.55367 3.41588 5.18672 4.2976 5.06512V5.06555Z" fill="url(#paint0_linear_319_5711)"/>
</g>
<defs>
<linearGradient id="paint0_linear_319_5711" x1="0.790113" y1="11.5706" x2="27.2783" y2="27.6814" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC80B"/>
<stop offset="1" stop-color="#E89318"/>
</linearGradient>
<clipPath id="clip0_319_5711">
<rect width="22" height="22" fill="white" transform="translate(0.77832 0.679688)"/>
</clipPath>
</defs>
</svg>
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_319_5704)">
<path d="M10.4369 20.6078C10.6568 20.7035 10.908 20.7035 11.1279 20.6078C13.7295 19.475 19.0666 14.0719 19.0666 8.95938C19.0666 4.38672 15.3596 0.679688 10.7869 0.679688H10.7693C6.19629 0.679688 2.48926 4.38672 2.48926 8.95938C2.48926 14.0723 7.83457 19.4754 10.4365 20.6078H10.4369ZM10.7291 4.36875C12.0232 4.36875 13.0725 5.41797 13.0725 6.71211C13.0725 8.00625 12.0232 9.05547 10.7291 9.05547C9.43496 9.05547 8.38574 8.00625 8.38574 6.71211C8.38574 5.41797 9.43496 4.36875 10.7291 4.36875ZM6.90762 12.3672C6.86582 11.9605 6.88418 11.6184 6.88418 11.618C6.88418 11.618 6.86582 11.3973 6.90762 10.9973C6.90762 10.9938 6.9084 10.9906 6.90879 10.9871C6.94316 10.659 7.09082 10.3535 7.32519 10.1187C7.55957 9.88477 7.87012 9.73281 8.20019 9.69727C8.68496 9.64609 10.1596 9.61797 10.9092 9.61797C11.6588 9.61797 12.8619 9.6457 13.3467 9.69727C13.6764 9.73242 13.9873 9.88477 14.2217 10.1187C14.4561 10.3535 14.6037 10.659 14.6381 10.9871C14.6381 10.9906 14.6389 10.9938 14.6393 10.9973C14.6811 11.3977 14.6756 11.6172 14.6756 11.6176C14.6756 11.6176 14.6811 11.9609 14.6396 12.368C14.6396 12.3715 14.6389 12.3746 14.6385 12.3781C14.6041 12.7117 14.4572 13.041 14.2236 13.3082C13.9904 13.575 13.6807 13.7664 13.3514 13.8359C12.8678 13.9453 11.6623 14.0531 10.91 14.048C10.1576 14.0668 8.67949 13.95 8.1959 13.8352C7.8666 13.7656 7.55684 13.5742 7.32363 13.3074C7.09043 13.0402 6.94316 12.7109 6.90879 12.3777C6.90879 12.3742 6.90801 12.3707 6.90762 12.3676V12.3672Z" fill="url(#paint0_linear_319_5704)"/>
<path d="M10.4369 20.6078C10.6568 20.7035 10.908 20.7035 11.1279 20.6078C13.7295 19.475 19.0666 14.0719 19.0666 8.95938C19.0666 4.38672 15.3596 0.679688 10.7869 0.679688H10.7693C6.19629 0.679688 2.48926 4.38672 2.48926 8.95938C2.48926 14.0723 7.83457 19.4754 10.4365 20.6078H10.4369ZM10.7291 4.36875C12.0232 4.36875 13.0725 5.41797 13.0725 6.71211C13.0725 8.00625 12.0232 9.05547 10.7291 9.05547C9.43496 9.05547 8.38574 8.00625 8.38574 6.71211C8.38574 5.41797 9.43496 4.36875 10.7291 4.36875ZM6.90762 12.3672C6.86582 11.9605 6.88418 11.6184 6.88418 11.618C6.88418 11.618 6.86582 11.3973 6.90762 10.9973C6.90762 10.9938 6.9084 10.9906 6.90879 10.9871C6.94316 10.659 7.09082 10.3535 7.32519 10.1187C7.55957 9.88477 7.87012 9.73281 8.20019 9.69727C8.68496 9.64609 10.1596 9.61797 10.9092 9.61797C11.6588 9.61797 12.8619 9.6457 13.3467 9.69727C13.6764 9.73242 13.9873 9.88477 14.2217 10.1187C14.4561 10.3535 14.6037 10.659 14.6381 10.9871C14.6381 10.9906 14.6389 10.9938 14.6393 10.9973C14.6811 11.3977 14.6756 11.6172 14.6756 11.6176C14.6756 11.6176 14.6811 11.9609 14.6396 12.368C14.6396 12.3715 14.6389 12.3746 14.6385 12.3781C14.6041 12.7117 14.4572 13.041 14.2236 13.3082C13.9904 13.575 13.6807 13.7664 13.3514 13.8359C12.8678 13.9453 11.6623 14.0531 10.91 14.048C10.1576 14.0668 8.67949 13.95 8.1959 13.8352C7.8666 13.7656 7.55684 13.5742 7.32363 13.3074C7.09043 13.0402 6.94316 12.7109 6.90879 12.3777C6.90879 12.3742 6.90801 12.3707 6.90762 12.3676V12.3672Z" fill="url(#paint1_linear_319_5704)"/>
</g>
<defs>
<linearGradient id="paint0_linear_319_5704" x1="2.48926" y1="10.6797" x2="19.0666" y2="10.6797" gradientUnits="userSpaceOnUse">
<stop stop-color="#0080DE"/>
<stop offset="0.6" stop-color="#49BCFF"/>
<stop offset="1" stop-color="#61CAFF"/>
</linearGradient>
<linearGradient id="paint1_linear_319_5704" x1="-6.72168" y1="-0.320313" x2="20.7783" y2="12.6797" gradientUnits="userSpaceOnUse">
<stop stop-color="#6949C6"/>
<stop offset="0.6" stop-color="#7C6EE0"/>
<stop offset="1" stop-color="#A28EEF"/>
</linearGradient>
<clipPath id="clip0_319_5704">
<rect width="20" height="20" fill="white" transform="translate(0.77832 0.679688)"/>
</clipPath>
</defs>
</svg>
class hrmAccessiblePagesResponse {
String? error;
List<PagesAccessible>? pagesAccessible;
String? message;
int? sessionExists;
hrmAccessiblePagesResponse(
{this.error, this.pagesAccessible, this.message, this.sessionExists});
hrmAccessiblePagesResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
if (json['pages_accessible'] != null) {
pagesAccessible = <PagesAccessible>[];
json['pages_accessible'].forEach((v) {
pagesAccessible!.add(new PagesAccessible.fromJson(v));
});
}
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.pagesAccessible != null) {
data['pages_accessible'] =
this.pagesAccessible!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
class PagesAccessible {
String? id;
String? pageName;
String? mode;
PagesAccessible({this.id, this.pageName, this.mode});
PagesAccessible.fromJson(Map<String, dynamic> json) {
id = json['id'];
pageName = json['page_name'];
mode = json['mode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['page_name'] = this.pageName;
data['mode'] = this.mode;
return data;
}
}
class leaveApplicationDetailsResponse {
RequestDetails? requestDetails;
String? error;
String? message;
int? sessionExists;
leaveApplicationDetailsResponse(
{this.requestDetails, this.error, this.message, this.sessionExists});
leaveApplicationDetailsResponse.fromJson(Map<String, dynamic> json) {
requestDetails = json['request_details'] != null
? new RequestDetails.fromJson(json['request_details'])
: null;
error = json['error'];
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.requestDetails != null) {
data['request_details'] = this.requestDetails!.toJson();
}
data['error'] = this.error;
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
class RequestDetails {
String? id;
String? appliedDate;
String? fromDate;
String? toDate;
String? fromTime;
String? toTime;
String? reason;
String? leaveType;
String? status;
String? requestedTo;
String? approvedBy;
String? approvedDate;
String? approvalRemarks;
RequestDetails(
{this.id,
this.appliedDate,
this.fromDate,
this.toDate,
this.fromTime,
this.toTime,
this.reason,
this.leaveType,
this.status,
this.requestedTo,
this.approvedBy,
this.approvedDate,
this.approvalRemarks});
RequestDetails.fromJson(Map<String, dynamic> json) {
id = json['id'];
appliedDate = json['applied_date'];
fromDate = json['from_date'];
toDate = json['to_date'];
fromTime = json['from_time'];
toTime = json['to_time'];
reason = json['reason'];
leaveType = json['leave_type'];
status = json['status'];
requestedTo = json['requested_to'];
approvedBy = json['approved_by'];
approvedDate = json['approved_date'];
approvalRemarks = json['approval_remarks'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['applied_date'] = this.appliedDate;
data['from_date'] = this.fromDate;
data['to_date'] = this.toDate;
data['from_time'] = this.fromTime;
data['to_time'] = this.toTime;
data['reason'] = this.reason;
data['leave_type'] = this.leaveType;
data['status'] = this.status;
data['requested_to'] = this.requestedTo;
data['approved_by'] = this.approvedBy;
data['approved_date'] = this.approvedDate;
data['approval_remarks'] = this.approvalRemarks;
return data;
}
}
class leaveApplicationLIstResponse {
List<RequestList>? requestList;
String? error;
String? message;
int? sessionExists;
leaveApplicationLIstResponse(
{this.requestList, this.error, this.message, this.sessionExists});
leaveApplicationLIstResponse.fromJson(Map<String, dynamic> json) {
if (json['request_list'] != null) {
requestList = <RequestList>[];
json['request_list'].forEach((v) {
requestList!.add(new RequestList.fromJson(v));
});
}
error = json['error'];
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.requestList != null) {
data['request_list'] = this.requestList!.map((v) => v.toJson()).toList();
}
data['error'] = this.error;
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
class RequestList {
String? id;
String? appliedDate;
String? fromPeriod;
String? toPeriod;
String? status;
String? leaveType;
RequestList(
{this.id, this.appliedDate, this.fromPeriod, this.toPeriod, this.status});
RequestList.fromJson(Map<String, dynamic> json) {
id = json['id'];
appliedDate = json['applied_date'];
fromPeriod = json['from_period'];
toPeriod = json['to_period'];
status = json['status'];
leaveType = json["leave_type"];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['applied_date'] = this.appliedDate;
data['from_period'] = this.fromPeriod;
data['to_period'] = this.toPeriod;
data['status'] = this.status;
data["leave_type"] = this.leaveType;
return data;
}
}
class tourExpensesAddViewResponse {
List<int>? daAmount;
List<String>? tourType;
List<String>? travelType;
String? error;
String? message;
int? sessionExists;
tourExpensesAddViewResponse(
{this.daAmount,
this.tourType,
this.travelType,
this.error,
this.message,
this.sessionExists});
tourExpensesAddViewResponse.fromJson(Map<String, dynamic> json) {
daAmount = json['da_amount'].cast<int>();
tourType = json['tour_type'].cast<String>();
travelType = json['travel_type'].cast<String>();
error = json['error'];
message = json['message'];
sessionExists = json['session_exists'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['da_amount'] = this.daAmount;
data['tour_type'] = this.tourType;
data['travel_type'] = this.travelType;
data['error'] = this.error;
data['message'] = this.message;
data['session_exists'] = this.sessionExists;
return data;
}
}
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 '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 '../../Utils/SharedpreferencesService.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;
......@@ -18,8 +20,114 @@ class Attendancelistprovider extends ChangeNotifier {
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
/// fetch aattendance request list
Future<void> fetchAttendanceRequests(context,String type, String from, String to) async {
// 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();
......@@ -27,36 +135,234 @@ class Attendancelistprovider extends ChangeNotifier {
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final result = await ApiCalling.attendanceRequestListAPI(provider.empId, provider.session, type, from, to);
debugPrint('empId: ${provider.empId}, session: ${provider.requestId}');
// 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();
}
DateTime? _date;
final TextEditingController dateController = TextEditingController();
/// --- 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);
void setDate(DateTime newDate) {
_date = newDate;
dateController.text =
"${newDate.day}-${newDate.month}-${newDate.year}";
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 (_date == null) {
setDate(DateTime.now());
if (_selectedDate == null) {
setSelectedDate(DateTime.now());
}
showCupertinoModalPopup<void>(
......@@ -72,36 +378,48 @@ class Attendancelistprovider extends ChangeNotifier {
top: false,
child: Column(
children: [
// Cancel + Done Buttons
SizedBox(
height: 40,
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: const Text("Cancel",
style: TextStyle(color: Colors.blue)),
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Cancel',
style: TextStyle(
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
onPressed: () => Navigator.pop(context),
),
CupertinoButton(
child: const Text("Done",
style: TextStyle(color: Colors.blue)),
child: Text(
'Done',
style: TextStyle(
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
onPressed: () {
setDate(_date ?? DateTime.now());
setSelectedDate(_selectedDate ?? DateTime.now());
Navigator.pop(context);
},
),
],
),
),
// Cupertino Date Picker
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: _date ?? DateTime.now(),
initialDateTime: _selectedDate ?? DateTime.now(),
mode: CupertinoDatePickerMode.date,
use24hFormat: true,
showDayOfWeek: true,
onDateTimeChanged: (DateTime newDate) {
_date = newDate; // temp update
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 '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';
......@@ -15,6 +19,37 @@ class TourExpensesProvider extends ChangeNotifier {
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;
......@@ -45,21 +80,115 @@ class TourExpensesProvider extends ChangeNotifier {
notifyListeners();
}
DateTime? _date;
final TextEditingController dateController = TextEditingController();
Future<void> fetchTourExpensesAddView(BuildContext context, String tourBillId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
void setDate(DateTime newDate) {
_date = newDate;
dateController.text = "${newDate.day}-${newDate.month}-${newDate.year}";
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();
}
void showDatePickerDialog(BuildContext context) {
if (_date == null) {
setDate(DateTime.now());
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;
showCupertinoModalPopup<void>(
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 250,
......@@ -73,18 +202,25 @@ class TourExpensesProvider extends ChangeNotifier {
child: Column(
children: [
SizedBox(
height: 40,
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: const Text("Cancel", style: TextStyle(color: Colors.blue)),
child: const Text("Cancel",
style: TextStyle(color: Colors.blue)),
onPressed: () => Navigator.pop(context),
),
CupertinoButton(
child: const Text("Done", style: TextStyle(color: Colors.blue)),
child: const Text("Done",
style: TextStyle(color: Colors.blue)),
onPressed: () {
setDate(_date ?? DateTime.now());
pickedDate = currentDate;
if (isFromDate) {
fromDateField.text = _formatDate(pickedDate!);
} else {
toDateField.text = _formatDate(pickedDate!);
}
Navigator.pop(context);
},
),
......@@ -94,10 +230,10 @@ class TourExpensesProvider extends ChangeNotifier {
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: _date ?? DateTime.now(),
initialDateTime: currentDate,
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
_date = newDate;
currentDate = newDate;
},
),
),
......@@ -106,5 +242,9 @@ class TourExpensesProvider extends ChangeNotifier {
),
),
);
return pickedDate;
}
}
......@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.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 'screens/notifierExports.dart';
import 'package:generp/Utils/SharedpreferencesService.dart';
......@@ -231,6 +232,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => TourExpensesProvider()),
ChangeNotifierProvider(create: (_) => TourExpensesDetailsProvider()),
ChangeNotifierProvider(create: (_) => RewardListProvider()),
ChangeNotifierProvider(create: (_) => LeaveApplicationListProvider()),
],
child: Builder(
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];
}
}
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;
}
}
......@@ -2,9 +2,11 @@ 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:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/hrmProvider/attendanceListProvider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
......@@ -17,6 +19,10 @@ class AddLiveAttendanceScreen extends StatefulWidget {
}
class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
String? typeError;
String? locationError;
String? proofError;
String? selectedType;
Dropdowntheme ddtheme = Dropdowntheme();
final TextEditingController locationController = TextEditingController();
......@@ -25,9 +31,9 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
final List<String> types = ["Check In", "Check Out"];
final ImagePicker picker = ImagePicker();
XFile? proofFile; // store selected proof
XFile? proofFile;
bool isSubmitting = false;
// computed labels
String get locationHeading =>
selectedType == null ? "Location" : "$selectedType Location";
......@@ -40,21 +46,49 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
bool get isSubmitEnabled =>
selectedType != null &&
locationController.text.trim().isNotEmpty &&
proofFile != null; // proof is required
proofFile != null;
@override
void initState() {
super.initState();
locationController.addListener(() {
setState(() {});
_autoFetchLocation();
}
Future<void> _autoFetchLocation() async {
String loc = await getCurrentLocation();
setState(() {
locationController.text = loc;
});
}
@override
void dispose() {
locationController.dispose();
descriptionController.dispose();
super.dispose();
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) {
......@@ -95,26 +129,71 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
);
}
Future<String> getCurrentLocation() async {
var status = await Permission.location.request();
if (status.isGranted) {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return "${position.latitude.toStringAsFixed(7)}, ${position.longitude.toStringAsFixed(7)}";
} else {
return "Permission denied";
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();
}
/// New: submit function
void submitAttendance() {
print("==== Attendance Submitted ====");
print("Type: $selectedType");
print("Location: ${locationController.text}");
print("Description: ${descriptionController.text}");
print("Proof: ${proofFile?.path ?? 'No file'}");
print("=============================");
void _showSnack(BuildContext context, String msg) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(msg), backgroundColor: Colors.black87),
);
}
@override
......@@ -123,7 +202,8 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFEFFFFFF),
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
InkResponse(
......@@ -138,7 +218,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
"Add Live Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
......@@ -155,7 +235,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
const Text("Type",
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Container(
......@@ -167,101 +247,113 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text("Select Type",
hint: const Text(
"Select Type",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400),
),
value: selectedType,
items: types
.map((e) =>
DropdownMenuItem<String>(
value: e,
child: Text(
e,
style: TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
)
))
.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),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
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 field
/// Location
Text(locationHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: locationController,
readOnly: true,
onTap: () async {
String loc = await getCurrentLocation();
locationController.text = loc;
},
decoration: _inputDecoration("Tap to get location"),
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: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)
),
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 20),
/// Attach Proof
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => _showPicker(context),
style: OutlinedButton.styleFrom(
backgroundColor: Colors.blue.shade50,
side: BorderSide(color: Colors.blue.shade200),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.symmetric(vertical: 14),
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: Text(proofButtonText,
child: Center(
child: Text(
proofButtonText,
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
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")),
],
/// Show proof preview
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
......@@ -269,9 +361,10 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text(
"Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis)),
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),
......@@ -282,23 +375,30 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
const SizedBox(height: 24),
/// Submit button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isSubmitEnabled ? submitAttendance : null,
style: ElevatedButton.styleFrom(
backgroundColor:
isSubmitEnabled ? Colors.blue : Colors.grey.shade400,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
/// 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,
),
),
child: const Text("Submit",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500)),
),
),
],
......@@ -306,13 +406,13 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400,
color: Colors.grey,
),
......@@ -324,7 +424,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue),
borderSide: BorderSide(color: AppColors.app_blue),
),
);
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/OrganizationStructureScreen.dart';
import 'package:generp/screens/hrm/RewardListScreen.dart';
import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart';
......@@ -121,97 +122,115 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
color: const Color(0xffEDF8FF),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
"assets/svg/hrm/groupIc.svg",
height: 29,
width: 29,
fit: BoxFit.contain,
),
const SizedBox(width: 7),
const Text(
"Organization Structure",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans"),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrganizationStructureScreen(),
),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
"assets/svg/hrm/groupIc.svg",
height: 29,
width: 29,
fit: BoxFit.contain,
),
const SizedBox(width: 7),
const Text(
"Organization Structure",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans"),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
),
],
),
),
/// Bottom Grid Section
Padding(
padding: const EdgeInsets.all(15),
child: GridView.count(
crossAxisCount: 2, // items per row
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 2.0, // tiles height
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTile(
label: "Attendance List",
subtitle: "Real-time request",
assetIcon: "assets/svg/hrm/attendanceList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Attendancelist(),
),
);
},
),
_buildTile(
label: "Leave Application",
subtitle: "Apply & Track",
assetIcon: "assets/svg/hrm/leaveApplication.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LeaveApplicationScreen(),
),
);
},
// Bottom Grid Section
LayoutBuilder(
builder: (context, constraints) {
final itemWidth = 180.0; // Fixed desired width for each item
final availableWidth = constraints.maxWidth;
final crossAxisCount = (availableWidth / itemWidth).floor().clamp(2, 4);
return Padding(
padding: const EdgeInsets.all(14),
child: GridView.count(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 1.7,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTile(
label: "Attendance List",
subtitle: "Real-time request",
assetIcon: "assets/svg/hrm/attendanceList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Attendancelist(),
),
);
},
),
_buildTile(
label: "Leave Application",
subtitle: "Apply & Track",
assetIcon: "assets/svg/hrm/leaveApplication.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LeaveApplicationListScreen(),
),
);
},
),
_buildTile(
label: "Rewards List",
subtitle: "Track earned rewards",
assetIcon: "assets/svg/hrm/rewardList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RewardListScreen(),
),
);
},
),
_buildTile(
label: "Tour Expenses",
subtitle: "Submit and manage claims",
assetIcon: "assets/svg/hrm/tourExp.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TourExpensesListScreen(),
),
);
},
),
],
),
_buildTile(
label: "Rewards List",
subtitle: "Track earned rewards",
assetIcon: "assets/svg/hrm/rewardList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RewardListScreen(),
),
);
},
),
_buildTile(
label: "Tour Expenses",
subtitle: "Submit and manage claims",
assetIcon: "assets/svg/hrm/tourExp.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TourExpensesListScreen(),
),
);
},
),
],
),
);
},
),
],
),
......@@ -228,73 +247,80 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
}
/// Reusable Tile Widget (Row style)
/// Reusable Tile Widget (Row style) - Updated to match design
Widget _buildTile({
required String label,
required String subtitle,
required String assetIcon, // SVG/PNG asset instead of IconData
required String assetIcon, // SVG/PNG asset
required Color txtColor,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(14),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 15,
),
margin: EdgeInsets.symmetric(
vertical: 7,
horizontal: 5,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// Left side text
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
label,
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
color: txtColor,
fontSize: 14,
color: AppColors.app_blue,
fontFamily: "JakartaMedium",
),
),
const SizedBox(height: 5),
SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
style: TextStyle(
fontSize: 12,
height: 1.4,
color: Color(0xff818181),
color: AppColors.grey_semi,
fontFamily: "JakartaMedium",
),
),
],
),
),
SizedBox(width: 10),
/// Right side icon (SVG/PNG)
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Container(
height: 48,
width: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 28,
width: 28,
assetIcon,
fit: BoxFit.contain,
),
flex: 1,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
assetIcon,
fit: BoxFit.contain,
),
),
),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment