Vaibhav_Feature-#768 #59

Closed
vaibhav.surve wants to merge 74 commits from Vaibhav_Feature-#768 into Feature_Expense
5 changed files with 312 additions and 391 deletions
Showing only changes of commit 797df80890 - Show all commits

View File

@ -11,6 +11,7 @@ class BaseBottomSheet extends StatelessWidget {
final String submitText; final String submitText;
final Color submitColor; final Color submitColor;
final IconData submitIcon; final IconData submitIcon;
final bool showButtons;
const BaseBottomSheet({ const BaseBottomSheet({
super.key, super.key,
@ -22,6 +23,7 @@ class BaseBottomSheet extends StatelessWidget {
this.submitText = 'Submit', this.submitText = 'Submit',
this.submitColor = Colors.indigo, this.submitColor = Colors.indigo,
this.submitIcon = Icons.check_circle_outline, this.submitIcon = Icons.check_circle_outline,
this.showButtons = true,
}); });
@override @override
@ -32,8 +34,7 @@ class BaseBottomSheet extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
padding: mediaQuery.viewInsets, padding: mediaQuery.viewInsets,
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(top: 60),
top: 60),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.cardColor, color: theme.cardColor,
@ -65,6 +66,7 @@ class BaseBottomSheet extends StatelessWidget {
MySpacing.height(12), MySpacing.height(12),
child, child,
MySpacing.height(24), MySpacing.height(24),
if (showButtons)
Row( Row(
children: [ children: [
Expanded( Expanded(
@ -81,8 +83,7 @@ class BaseBottomSheet extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 8),
vertical: 8),
), ),
), ),
), ),
@ -101,8 +102,7 @@ class BaseBottomSheet extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 8),
vertical: 8),
), ),
), ),
), ),

View File

@ -5,16 +5,17 @@ import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
import 'package:marco/helpers/utils/attendance_actions.dart'; import 'package:marco/helpers/utils/attendance_actions.dart';
import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/project_controller.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class AttendanceActionButton extends StatefulWidget { class AttendanceActionButton extends StatefulWidget {
final dynamic employee; final dynamic employee;
final AttendanceController attendanceController; final AttendanceController attendanceController;
const AttendanceActionButton({ const AttendanceActionButton({
Key? key, super.key,
required this.employee, required this.employee,
required this.attendanceController, required this.attendanceController,
}) : super(key: key); });
@override @override
State<AttendanceActionButton> createState() => _AttendanceActionButtonState(); State<AttendanceActionButton> createState() => _AttendanceActionButtonState();
@ -24,35 +25,39 @@ Future<String?> _showCommentBottomSheet(
BuildContext context, String actionText) async { BuildContext context, String actionText) async {
final TextEditingController commentController = TextEditingController(); final TextEditingController commentController = TextEditingController();
String? errorText; String? errorText;
Get.find<ProjectController>().selectedProject?.id;
return showModalBottomSheet<String>( return showModalBottomSheet<String>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.white, backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)), borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
), ),
builder: (context) { builder: (context) {
return StatefulBuilder( return StatefulBuilder(
builder: (context, setModalState) { builder: (context, setModalState) {
void submit() {
final comment = commentController.text.trim();
if (comment.isEmpty) {
setModalState(() => errorText = 'Comment cannot be empty.');
return;
}
Navigator.of(context).pop(comment);
}
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 16, bottom: MediaQuery.of(context).viewInsets.bottom,
right: 16,
top: 24,
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
), ),
child: BaseBottomSheet(
title: 'Add Comment for ${capitalizeFirstLetter(actionText)}',
onCancel: () => Navigator.of(context).pop(),
onSubmit: submit,
isSubmitting: false,
submitText: 'Submit',
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(
'Add Comment for ${capitalizeFirstLetter(actionText)}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 16),
TextField( TextField(
controller: commentController, controller: commentController,
maxLines: 4, maxLines: 4,
@ -71,34 +76,8 @@ Future<String?> _showCommentBottomSheet(
} }
}, },
), ),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () {
final comment = commentController.text.trim();
if (comment.isEmpty) {
setModalState(() {
errorText = 'Comment cannot be empty.';
});
return;
}
Navigator.of(context).pop(comment);
},
child: const Text('Submit'),
),
),
], ],
), ),
],
), ),
); );
}, },
@ -119,13 +98,15 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
void initState() { void initState() {
super.initState(); super.initState();
uniqueLogKey = AttendanceButtonHelper.getUniqueKey( uniqueLogKey = AttendanceButtonHelper.getUniqueKey(
widget.employee.employeeId, widget.employee.id); widget.employee.employeeId,
widget.employee.id,
);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!widget.attendanceController.uploadingStates widget.attendanceController.uploadingStates.putIfAbsent(
.containsKey(uniqueLogKey)) { uniqueLogKey,
widget.attendanceController.uploadingStates[uniqueLogKey] = false.obs; () => false.obs,
} );
}); });
} }
@ -167,6 +148,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
return selectedDateTime; return selectedDateTime;
} }
return null; return null;
} }
@ -228,7 +210,6 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
DateTime? selectedTime; DateTime? selectedTime;
// New condition: Yesterday Check-In + CheckOut action
final isYesterdayCheckIn = widget.employee.checkIn != null && final isYesterdayCheckIn = widget.employee.checkIn != null &&
DateUtils.isSameDay( DateUtils.isSameDay(
widget.employee.checkIn, widget.employee.checkIn,
@ -257,15 +238,16 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
} }
bool success = false; bool success = false;
if (actionText == ButtonActions.requestRegularize) { if (actionText == ButtonActions.requestRegularize) {
final regularizeTime = selectedTime ?? final regularizeTime = selectedTime ??
await showTimePickerForRegularization( await showTimePickerForRegularization(
context: context, context: context,
checkInTime: widget.employee.checkIn!, checkInTime: widget.employee.checkIn!,
); );
if (regularizeTime != null) { if (regularizeTime != null) {
final formattedSelectedTime = final formattedTime = DateFormat("hh:mm a").format(regularizeTime);
DateFormat("hh:mm a").format(regularizeTime);
success = await widget.attendanceController.captureAndUploadAttendance( success = await widget.attendanceController.captureAndUploadAttendance(
widget.employee.id, widget.employee.id,
widget.employee.employeeId, widget.employee.employeeId,
@ -273,12 +255,11 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
comment: userComment, comment: userComment,
action: updatedAction, action: updatedAction,
imageCapture: imageCapture, imageCapture: imageCapture,
markTime: formattedSelectedTime, markTime: formattedTime,
); );
} }
} else if (selectedTime != null) { } else if (selectedTime != null) {
// If selectedTime was picked in the new condition final formattedTime = DateFormat("hh:mm a").format(selectedTime);
final formattedSelectedTime = DateFormat("hh:mm a").format(selectedTime);
success = await widget.attendanceController.captureAndUploadAttendance( success = await widget.attendanceController.captureAndUploadAttendance(
widget.employee.id, widget.employee.id,
widget.employee.employeeId, widget.employee.employeeId,
@ -286,7 +267,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
comment: userComment, comment: userComment,
action: updatedAction, action: updatedAction,
imageCapture: imageCapture, imageCapture: imageCapture,
markTime: formattedSelectedTime, markTime: formattedTime,
); );
} else { } else {
success = await widget.attendanceController.captureAndUploadAttendance( success = await widget.attendanceController.captureAndUploadAttendance(
@ -312,8 +293,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
if (success) { if (success) {
widget.attendanceController.fetchEmployeesByProject(selectedProjectId); widget.attendanceController.fetchEmployeesByProject(selectedProjectId);
widget.attendanceController.fetchAttendanceLogs(selectedProjectId); widget.attendanceController.fetchAttendanceLogs(selectedProjectId);
await widget.attendanceController await widget.attendanceController.fetchRegularizationLogs(selectedProjectId);
.fetchRegularizationLogs(selectedProjectId);
await widget.attendanceController.fetchProjectData(selectedProjectId); await widget.attendanceController.fetchProjectData(selectedProjectId);
widget.attendanceController.update(); widget.attendanceController.update();
} }
@ -327,12 +307,19 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
false; false;
final isYesterday = AttendanceButtonHelper.isLogFromYesterday( final isYesterday = AttendanceButtonHelper.isLogFromYesterday(
widget.employee.checkIn, widget.employee.checkOut); widget.employee.checkIn,
widget.employee.checkOut,
);
final isTodayApproved = AttendanceButtonHelper.isTodayApproved( final isTodayApproved = AttendanceButtonHelper.isTodayApproved(
widget.employee.activity, widget.employee.checkIn); widget.employee.activity,
final isApprovedButNotToday = widget.employee.checkIn,
AttendanceButtonHelper.isApprovedButNotToday( );
widget.employee.activity, isTodayApproved);
final isApprovedButNotToday = AttendanceButtonHelper.isApprovedButNotToday(
widget.employee.activity,
isTodayApproved,
);
final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled( final isButtonDisabled = AttendanceButtonHelper.isButtonDisabled(
isUploading: isUploading, isUploading: isUploading,
@ -359,8 +346,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
isButtonDisabled: isButtonDisabled, isButtonDisabled: isButtonDisabled,
buttonText: buttonText, buttonText: buttonText,
buttonColor: buttonColor, buttonColor: buttonColor,
onPressed: onPressed: isButtonDisabled ? null : () => _handleButtonPressed(context),
isButtonDisabled ? null : () => _handleButtonPressed(context),
); );
}); });
} }
@ -374,20 +360,20 @@ class AttendanceActionButtonUI extends StatelessWidget {
final VoidCallback? onPressed; final VoidCallback? onPressed;
const AttendanceActionButtonUI({ const AttendanceActionButtonUI({
Key? key, super.key,
required this.isUploading, required this.isUploading,
required this.isButtonDisabled, required this.isButtonDisabled,
required this.buttonText, required this.buttonText,
required this.buttonColor, required this.buttonColor,
required this.onPressed, required this.onPressed,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 30, height: 30,
child: ElevatedButton( child: ElevatedButton(
onPressed: isButtonDisabled ? null : onPressed, onPressed: onPressed,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: buttonColor, backgroundColor: buttonColor,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12),

View File

@ -4,6 +4,7 @@ import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/utils/permission_constants.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class AttendanceFilterBottomSheet extends StatefulWidget { class AttendanceFilterBottomSheet extends StatefulWidget {
final AttendanceController controller; final AttendanceController controller;
@ -62,20 +63,20 @@ class _AttendanceFilterBottomSheetState
List<Widget> widgets = [ List<Widget> widgets = [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), padding: const EdgeInsets.only(bottom: 4),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: MyText.titleSmall( child: MyText.titleSmall("View", fontWeight: 600),
"View",
fontWeight: 600,
),
), ),
), ),
...filteredViewOptions.map((item) { ...filteredViewOptions.map((item) {
return RadioListTile<String>( return RadioListTile<String>(
dense: true, dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12), contentPadding: EdgeInsets.zero,
title: Text(item['label']!), title: MyText.bodyMedium(
item['label']!,
fontWeight: 500,
),
value: item['value']!, value: item['value']!,
groupValue: tempSelectedTab, groupValue: tempSelectedTab,
onChanged: (value) => setState(() => tempSelectedTab = value!), onChanged: (value) => setState(() => tempSelectedTab = value!),
@ -87,18 +88,13 @@ class _AttendanceFilterBottomSheetState
widgets.addAll([ widgets.addAll([
const Divider(), const Divider(),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), padding: const EdgeInsets.only(top: 12, bottom: 4),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: MyText.titleSmall( child: MyText.titleSmall("Date Range", fontWeight: 600),
"Date Range",
fontWeight: 600,
), ),
), ),
), InkWell(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
onTap: () => widget.controller.selectDateRangeForAttendance( onTap: () => widget.controller.selectDateRangeForAttendance(
context, context,
@ -110,21 +106,16 @@ class _AttendanceFilterBottomSheetState
border: Border.all(color: Colors.grey.shade400), border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
padding: padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row( child: Row(
children: [ children: [
Icon(Icons.date_range, color: Colors.black87), const Icon(Icons.date_range, color: Colors.black87),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: MyText.bodyMedium(
getLabelText(), getLabelText(),
style: const TextStyle( fontWeight: 500,
fontSize: 16,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
), ),
), ),
const Icon(Icons.arrow_drop_down, color: Colors.black87), const Icon(Icons.arrow_drop_down, color: Colors.black87),
@ -132,7 +123,6 @@ class _AttendanceFilterBottomSheetState
), ),
), ),
), ),
),
]); ]);
} }
@ -141,49 +131,20 @@ class _AttendanceFilterBottomSheetState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return ClipRRect(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
child: SingleChildScrollView( child: BaseBottomSheet(
child: Column( title: "Attendance Filter",
mainAxisSize: MainAxisSize.min, onCancel: () => Navigator.pop(context),
children: [ onSubmit: () => Navigator.pop(context, {
Padding(
padding: const EdgeInsets.only(top: 12, bottom: 8),
child: Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(4),
),
),
),
),
...buildMainFilters(),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 95, 132, 255),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Apply Filter'),
onPressed: () {
Navigator.pop(context, {
'selectedTab': tempSelectedTab, 'selectedTab': tempSelectedTab,
}); }),
}, submitText: "Apply Filter",
), submitIcon: Icons.filter_alt_outlined,
), submitColor: const Color.fromARGB(255, 95, 132, 255),
), child: Column(
], crossAxisAlignment: CrossAxisAlignment.start,
children: buildMainFilters(),
), ),
), ),
); );

View File

@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/utils/attendance_actions.dart'; import 'package:marco/helpers/utils/attendance_actions.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class AttendanceLogViewButton extends StatelessWidget { class AttendanceLogViewButton extends StatelessWidget {
final dynamic employee; final dynamic employee;
final dynamic attendanceController; // Use correct types as needed final dynamic attendanceController;
const AttendanceLogViewButton({ const AttendanceLogViewButton({
Key? key, Key? key,
required this.employee, required this.employee,
@ -50,41 +50,21 @@ class AttendanceLogViewButton extends StatelessWidget {
void _showLogsBottomSheet(BuildContext context) async { void _showLogsBottomSheet(BuildContext context) async {
await attendanceController.fetchLogsView(employee.id.toString()); await attendanceController.fetchLogsView(employee.id.toString());
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)), borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
), ),
backgroundColor: Theme.of(context).cardColor, backgroundColor: Colors.transparent,
builder: (context) => Padding( builder: (context) => BaseBottomSheet(
padding: EdgeInsets.only( title: "Attendance Log",
left: 16, onCancel: () => Navigator.pop(context),
right: 16, onSubmit: () => Navigator.pop(context),
top: 16, showButtons: false,
bottom: MediaQuery.of(context).viewInsets.bottom + 16, child: attendanceController.attendenceLogsView.isEmpty
), ? Padding(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.titleMedium(
"Attendance Log",
fontWeight: 700,
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
],
),
const SizedBox(height: 12),
if (attendanceController.attendenceLogsView.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0), padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Column( child: Column(
children: const [ children: const [
@ -94,8 +74,7 @@ class AttendanceLogViewButton extends StatelessWidget {
], ],
), ),
) )
else : ListView.separated(
ListView.separated(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: attendanceController.attendenceLogsView.length, itemCount: attendanceController.attendenceLogsView.length,
@ -155,13 +134,11 @@ class AttendanceLogViewButton extends StatelessWidget {
log.longitude != null) log.longitude != null)
GestureDetector( GestureDetector(
onTap: () { onTap: () {
final lat = double.tryParse(log final lat = double.tryParse(
.latitude log.latitude.toString()) ??
.toString()) ??
0.0; 0.0;
final lon = double.tryParse(log final lon = double.tryParse(
.longitude log.longitude.toString()) ??
.toString()) ??
0.0; 0.0;
if (lat >= -90 && if (lat >= -90 &&
lat <= 90 && lat <= 90 &&
@ -214,8 +191,7 @@ class AttendanceLogViewButton extends StatelessWidget {
height: 60, height: 60,
width: 60, width: 60,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: errorBuilder: (context, error, stackTrace) {
(context, error, stackTrace) {
return const Icon(Icons.broken_image, return const Icon(Icons.broken_image,
size: 20, color: Colors.grey); size: 20, color: Colors.grey);
}, },
@ -231,9 +207,6 @@ class AttendanceLogViewButton extends StatelessWidget {
), ),
); );
}, },
)
],
),
), ),
), ),
); );

View File

@ -157,7 +157,8 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
Map<String, dynamic>>( Map<String, dynamic>>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.white,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: Radius.circular(12)), top: Radius.circular(12)),