From bec72a312336c2c821cc8565bdf22917e5298d4a Mon Sep 17 00:00:00 2001 From: Mahesh1772 <87927591+Mahesh1772@users.noreply.github.com> Date: Wed, 25 Dec 2024 16:51:45 +0800 Subject: [PATCH 1/4] Reformat time stamps across attendance --- .../data/models/attendance_model.dart | 3 + .../attendance_repository_impl.dart | 84 ++++++++----- .../presentation/pages/attendance_tab.dart | 4 +- .../pages/edit_attendance_page.dart | 114 ++---------------- .../presentation/widgets/attendance_tile.dart | 4 +- 5 files changed, 71 insertions(+), 138 deletions(-) diff --git a/trooptrak_final_application/lib/features/detailed_view/data/models/attendance_model.dart b/trooptrak_final_application/lib/features/detailed_view/data/models/attendance_model.dart index 3b74a707..ef205493 100644 --- a/trooptrak_final_application/lib/features/detailed_view/data/models/attendance_model.dart +++ b/trooptrak_final_application/lib/features/detailed_view/data/models/attendance_model.dart @@ -1,6 +1,9 @@ import '../../domain/entities/attendance_record.dart'; +import 'package:intl/intl.dart'; class AttendanceModel extends AttendanceRecord { + static final DateFormat standardFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); + AttendanceModel({ required super.id, required super.dateTime, diff --git a/trooptrak_final_application/lib/features/detailed_view/data/repositories/attendance_repository_impl.dart b/trooptrak_final_application/lib/features/detailed_view/data/repositories/attendance_repository_impl.dart index e2ba9d67..08de02d8 100644 --- a/trooptrak_final_application/lib/features/detailed_view/data/repositories/attendance_repository_impl.dart +++ b/trooptrak_final_application/lib/features/detailed_view/data/repositories/attendance_repository_impl.dart @@ -6,6 +6,7 @@ import '../models/attendance_model.dart'; class AttendanceRepositoryImpl implements AttendanceRepository { final FirebaseFirestore _firestore; + final DateFormat standardFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); AttendanceRepositoryImpl(this._firestore); @@ -24,37 +25,54 @@ class AttendanceRepositoryImpl implements AttendanceRepository { @override Stream updateAttendance(String userId, AttendanceRecord record) async* { - final batch = _firestore.batch(); - - // Update the attendance record - final attendanceRef = _firestore - .collection('Users') - .doc(userId) - .collection('Attendance') - .doc(record.id); - batch.update(attendanceRef, { - 'date&time': record.dateTime, - 'isInsideCamp': record.isInsideCamp, - }); + try { + print("Attempting to update document with ID: ${record.id}"); + final batch = _firestore.batch(); - // Check if this is the latest attendance record - final latestAttendance = await _firestore - .collection('Users') - .doc(userId) - .collection('Attendance') - .orderBy('date&time', descending: true) - .limit(1) - .get(); - - if (latestAttendance.docs.isNotEmpty && latestAttendance.docs.first.id == record.id) { - // If it's the latest record, update the user's currentAttendance - final userRef = _firestore.collection('Users').doc(userId); - batch.update(userRef, { - 'currentAttendance': record.isInsideCamp ? 'Inside Camp' : 'Outside', + final attendanceRef = _firestore + .collection('Users') + .doc(userId) + .collection('Attendance') + .doc(record.id); + + // Let's first check if the document exists and print its data + final docSnapshot = await attendanceRef.get(); + print("Document exists: ${docSnapshot.exists}"); + if (docSnapshot.exists) { + print("Current document data: ${docSnapshot.data()}"); + } + + if (!docSnapshot.exists) { + throw Exception('Attendance record not found for ID: ${record.id}'); + } + + batch.update(attendanceRef, { + 'date&time': record.dateTime, + 'isInsideCamp': record.isInsideCamp, }); - } - yield* Stream.fromFuture(batch.commit()); + // Check if this is the latest attendance record + final latestAttendance = await _firestore + .collection('Users') + .doc(userId) + .collection('Attendance') + .orderBy('date&time', descending: true) + .limit(1) + .get(); + + if (latestAttendance.docs.isNotEmpty && + latestAttendance.docs.first.id == record.id) { + final userRef = _firestore.collection('Users').doc(userId); + batch.update(userRef, { + 'currentAttendance': record.isInsideCamp ? 'Inside Camp' : 'Outside', + }); + } + + yield* Stream.fromFuture(batch.commit()); + } catch (e) { + print("Detailed error in updateAttendance: $e"); + throw Exception('Failed to update attendance record: $e'); + } } @override @@ -73,20 +91,20 @@ class AttendanceRepositoryImpl implements AttendanceRepository { Stream updateUserAttendance(String userId, bool isInsideCamp) async* { final batch = _firestore.batch(); - // Create a new attendance record - String docId = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); + final now = DateTime.now(); + final String standardizedDateTime = standardFormat.format(now); + final attendanceRef = _firestore .collection('Users') .doc(userId) .collection('Attendance') - .doc(docId); + .doc(standardizedDateTime); // Using standardized format for document ID batch.set(attendanceRef, { - 'date&time': DateFormat('E d MMM yyyy HH:mm:ss').format(DateTime.now()), + 'date&time': standardizedDateTime, 'isInsideCamp': isInsideCamp, }); - // Update the user's currentAttendance final userRef = _firestore.collection('Users').doc(userId); batch.update(userRef, { 'currentAttendance': isInsideCamp ? 'Inside Camp' : 'Outside', diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart index 782f3ba4..0ca664ce 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart @@ -13,13 +13,15 @@ class AttendanceTab extends StatelessWidget { const AttendanceTab({super.key, required this.userId}); void _navigateToEdit(BuildContext context, nominal_roll.AttendanceRecord record) { + print("Original record datetime: ${record.dateTime}"); + Navigator.push( context, MaterialPageRoute( builder: (context) => EditAttendancePage( userId: userId, record: detailed_view.AttendanceRecord( - id: DateTime.now().toString(), // Generate a temporary ID + id: record.dateTime, dateTime: record.dateTime, isInsideCamp: record.isInsideCamp, ), diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/edit_attendance_page.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/edit_attendance_page.dart index e197a7e5..8554e498 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/edit_attendance_page.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/edit_attendance_page.dart @@ -22,11 +22,12 @@ class EditAttendancePage extends StatefulWidget { class _EditAttendancePageState extends State { late DateTime selectedDateTime; + final DateFormat standardFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); @override void initState() { super.initState(); - selectedDateTime = DateFormat("EEE d MMM yyyy HH:mm:ss").parse(widget.record.dateTime); + selectedDateTime = standardFormat.parse(widget.record.dateTime); } @override @@ -106,7 +107,7 @@ class _EditAttendancePageState extends State { ), SizedBox(height: 4.h), Text( - DateFormat('EEE d MMM yyyy HH:mm:ss').format(selectedDateTime), + standardFormat.format(selectedDateTime), style: GoogleFonts.poppins( color: theme.colorScheme.tertiary, fontSize: 16.sp, @@ -127,63 +128,6 @@ class _EditAttendancePageState extends State { ), SizedBox(height: 24.h), - // Status Switch - Container( - height: 70.h, - decoration: BoxDecoration( - color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : theme.colorScheme.surface, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: isDarkMode ? Colors.black.withOpacity(0.3) : Colors.black.withOpacity(0.1), - blurRadius: 4.r, - offset: Offset(0, 2.h), - ), - ], - ), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Status', - style: GoogleFonts.poppins( - color: theme.colorScheme.tertiary.withOpacity(0.7), - fontSize: 14.sp, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 4.h), - Text( - widget.record.isInsideCamp ? 'Inside Camp' : 'Outside Camp', - style: GoogleFonts.poppins( - color: theme.colorScheme.tertiary, - fontSize: 16.sp, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - Switch( - value: widget.record.isInsideCamp, - onChanged: (bool value) { - setState(() { - widget.record.isInsideCamp = value; - }); - }, - activeColor: theme.colorScheme.secondary, - ), - ], - ), - ), - ), - SizedBox(height: 32.h), - // Save Button SizedBox( width: double.infinity, @@ -227,54 +171,14 @@ class _EditAttendancePageState extends State { initialDate: selectedDateTime, firstDate: DateTime(2000), lastDate: DateTime(2100), - builder: (context, child) { - final theme = Theme.of(context); - final isDarkMode = theme.brightness == Brightness.dark; - return Theme( - data: Theme.of(context).copyWith( - colorScheme: theme.colorScheme.copyWith( - primary: theme.colorScheme.secondary, - onPrimary: Colors.white, - surface: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, - onSurface: isDarkMode ? Colors.white : theme.colorScheme.onSurface, - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.secondary, - ), - ), - dialogBackgroundColor: isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, - ), - child: child!, - ); - }, ); + if (date != null) { final time = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(selectedDateTime), - builder: (context, child) { - final theme = Theme.of(context); - final isDarkMode = theme.brightness == Brightness.dark; - return Theme( - data: Theme.of(context).copyWith( - colorScheme: theme.colorScheme.copyWith( - primary: theme.colorScheme.secondary, - onPrimary: Colors.white, - surface: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, - onSurface: isDarkMode ? Colors.white : theme.colorScheme.onSurface, - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.secondary, - ), - ), - dialogBackgroundColor: isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, - ), - child: child!, - ); - }, ); + if (time != null) { setState(() { selectedDateTime = DateTime( @@ -290,10 +194,13 @@ class _EditAttendancePageState extends State { } void _saveChanges() { + print("Updating record with ID: ${widget.record.id}"); + print("New datetime: ${standardFormat.format(selectedDateTime)}"); + final updatedRecord = AttendanceRecord( id: widget.record.id, - dateTime: DateFormat('EEE d MMM yyyy HH:mm:ss').format(selectedDateTime), - isInsideCamp: widget.record.isInsideCamp, // Maintain original status + dateTime: standardFormat.format(selectedDateTime), + isInsideCamp: widget.record.isInsideCamp, ); final attendanceProvider = Provider.of(context, listen: false); @@ -314,6 +221,7 @@ class _EditAttendancePageState extends State { Navigator.of(context).pop(); }, onError: (error) { + print("Error details: $error"); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart index 6431615a..fbb80196 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart @@ -3,13 +3,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../domain/entities/attendance_record.dart'; +import 'package:intl/intl.dart'; class AttendanceTile extends StatelessWidget { final AttendanceRecord record; final Function(AttendanceRecord) onEdit; final Function(String) onDelete; + final DateFormat standardFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); - const AttendanceTile({ + AttendanceTile({ super.key, required this.record, required this.onEdit, From f918b5b999aa94c69349e5e587fa5fddd2f6b728 Mon Sep 17 00:00:00 2001 From: Mahesh1772 <87927591+Mahesh1772@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:06:00 +0800 Subject: [PATCH 2/4] Fix slider working --- .../presentation/pages/attendance_tab.dart | 251 +++--------------- .../providers/user_detail_provider.dart | 14 +- .../presentation/widgets/user_tile.dart | 35 ++- 3 files changed, 84 insertions(+), 216 deletions(-) diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart index 0ca664ce..56ac661f 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; -import '../../../nominal_roll/domain/entities/attendance_record.dart' as nominal_roll; +import 'package:trooptrak_final_application/features/detailed_view/presentation/widgets/attendance_tile.dart'; import '../../domain/entities/attendance_record.dart' as detailed_view; import '../../../nominal_roll/presentation/providers/user_detail_provider.dart'; +import '../providers/attendance_provider.dart'; import 'edit_attendance_page.dart'; class AttendanceTab extends StatelessWidget { @@ -12,7 +11,7 @@ class AttendanceTab extends StatelessWidget { const AttendanceTab({super.key, required this.userId}); - void _navigateToEdit(BuildContext context, nominal_roll.AttendanceRecord record) { + void _navigateToEdit(BuildContext context, detailed_view.AttendanceRecord record) { print("Original record datetime: ${record.dateTime}"); Navigator.push( @@ -20,228 +19,62 @@ class AttendanceTab extends StatelessWidget { MaterialPageRoute( builder: (context) => EditAttendancePage( userId: userId, - record: detailed_view.AttendanceRecord( - id: record.dateTime, - dateTime: record.dateTime, - isInsideCamp: record.isInsideCamp, - ), + record: record, ), ), ); } + void _deleteRecord(BuildContext context, String recordId) { + final attendanceProvider = Provider.of(context, listen: false); + attendanceProvider.deleteAttendanceRecord(userId, recordId).listen( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Attendance record deleted successfully'), + backgroundColor: Colors.green, + ), + ); + }, + onError: (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error deleting attendance record: $error'), + backgroundColor: Colors.red, + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDarkMode = theme.brightness == Brightness.dark; return Scaffold( backgroundColor: Colors.transparent, body: Consumer( builder: (context, provider, child) { - return StreamBuilder>( + return StreamBuilder>( stream: provider.getUserAttendance(userId), - builder: (context, snapshot) { + builder: (context, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: CircularProgressIndicator( - color: theme.colorScheme.secondary, - strokeWidth: 2.w, - ), - ); - } - - if (snapshot.hasError) { - return Center( - child: Text( - 'Error: ${snapshot.error}', - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.error, - letterSpacing: 1.2, - fontSize: 14.sp, - ), - ), - ); + return const Center(child: CircularProgressIndicator()); } - final records = snapshot.data ?? []; - if (records.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.error_outline_rounded, - size: 32.sp, - color: theme.colorScheme.error, - ), - SizedBox(height: 8.h), - Text( - 'No attendance records found', - style: theme.textTheme.titleLarge?.copyWith( - color: theme.colorScheme.tertiary, - letterSpacing: 1.2, - fontSize: 16.sp, - ), - ), - ], - ), - ); + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text('No attendance records found')); } - return Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: isDarkMode - ? const Color.fromARGB(255, 45, 50, 65) - : Colors.white, - borderRadius: BorderRadius.circular(8.r), - boxShadow: [ - BoxShadow( - color: isDarkMode - ? Colors.black.withOpacity(0.3) - : Colors.black.withOpacity(0.1), - blurRadius: 4.r, - offset: Offset(0, 2.h), - ), - ], - ), - child: Icon( - Icons.outbond, - size: 20.sp, - color: theme.colorScheme.tertiary, - ), - ), - SizedBox(width: 8.w), - Text( - "Book In / Book Out", - style: theme.textTheme.titleLarge?.copyWith( - color: theme.colorScheme.tertiary, - letterSpacing: 1.2, - fontSize: 18.sp, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - SizedBox(height: 12.h), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: records.length, - padding: EdgeInsets.symmetric(vertical: 4.h), - itemBuilder: (context, index) { - final record = records[index]; - return Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - SlidableAction( - onPressed: (context) => _navigateToEdit(context, record), - backgroundColor: theme.colorScheme.secondary, - foregroundColor: Colors.white, - icon: Icons.edit_rounded, - label: 'Edit', - borderRadius: BorderRadius.horizontal( - right: Radius.circular(12.r), - ), - ), - ], - ), - child: InkWell( - onTap: () => _navigateToEdit(context, record), - child: Container( - margin: EdgeInsets.only(bottom: 12.h), - padding: EdgeInsets.all(12.w), - decoration: BoxDecoration( - color: isDarkMode - ? const Color.fromARGB(255, 45, 50, 65) - : theme.colorScheme.surface, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: isDarkMode ? Colors.black.withOpacity(0.5) : Colors.black.withOpacity(0.1), - blurRadius: 8.r, - offset: Offset(0, 4.h), - spreadRadius: isDarkMode ? 1.r : 0.r, - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - record.dateTime, - style: theme.textTheme.titleMedium?.copyWith( - color: isDarkMode - ? Colors.white - : theme.colorScheme.tertiary, - letterSpacing: 1.2, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ), - SizedBox(height: 4.h), - Text( - record.isInsideCamp ? 'Inside Camp' : 'Outside Camp', - style: theme.textTheme.titleSmall?.copyWith( - color: record.isInsideCamp - ? (isDarkMode ? const Color.fromARGB(255, 130, 100, 255) : theme.colorScheme.secondary) - : (isDarkMode ? const Color.fromARGB(255, 255, 100, 100) : theme.colorScheme.error), - letterSpacing: 1.2, - fontSize: 12.sp, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - Row( - children: [ - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: record.isInsideCamp - ? (isDarkMode ? const Color.fromARGB(255, 130, 100, 255) : theme.colorScheme.secondary) - : (isDarkMode ? const Color.fromARGB(255, 255, 100, 100) : theme.colorScheme.error), - borderRadius: BorderRadius.circular(8.r), - ), - child: Icon( - record.isInsideCamp - ? Icons.check_circle_outline_rounded - : Icons.cancel_outlined, - color: Colors.white, - size: 20.sp, - ), - ), - SizedBox(width: 8.w), - Icon( - Icons.chevron_right_rounded, - color: theme.colorScheme.tertiary.withOpacity(0.5), - size: 24.sp, - ), - ], - ), - ], - ), - ), - ), - ); - }, - ), - ], - ), - ), + final records = snapshot.data!; + return ListView.builder( + itemCount: records.length, + itemBuilder: (context, index) { + final record = records[index]; + return AttendanceTile( + record: record, + onEdit: (record) => _navigateToEdit(context, record), + onDelete: (recordId) => _deleteRecord(context, recordId), + ); + }, ); }, ); diff --git a/trooptrak_final_application/lib/features/nominal_roll/presentation/providers/user_detail_provider.dart b/trooptrak_final_application/lib/features/nominal_roll/presentation/providers/user_detail_provider.dart index 1db40133..e416965f 100644 --- a/trooptrak_final_application/lib/features/nominal_roll/presentation/providers/user_detail_provider.dart +++ b/trooptrak_final_application/lib/features/nominal_roll/presentation/providers/user_detail_provider.dart @@ -2,11 +2,11 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/foundation.dart'; import 'package:trooptrak_final_application/features/nominal_roll/domain/usecases/delete_user_usecase.dart'; import '../../domain/entities/user.dart'; -import '../../domain/entities/attendance_record.dart'; import '../../domain/usecases/get_user_by_id_usecase.dart'; import '../../domain/usecases/get_user_attendance_usecase.dart'; -import 'dart:async'; import '../../domain/usecases/update_user_usecase.dart'; +import '../../../detailed_view/domain/entities/attendance_record.dart' as detailed_view; +import 'dart:async'; class UserDetailProvider extends ChangeNotifier { final GetUserByIdUseCase getUserByIdUseCase; @@ -73,8 +73,14 @@ class UserDetailProvider extends ChangeNotifier { ); } - Stream> getUserAttendance(String id) { - return getUserAttendanceUseCase(id); + Stream> getUserAttendance(String userId) { + return getUserAttendanceUseCase(userId).map((records) { + return records.map((record) => detailed_view.AttendanceRecord( + id: record.dateTime, + dateTime: record.dateTime, + isInsideCamp: record.isInsideCamp, + )).toList(); + }); } Future updateUser(User updatedUser) async { diff --git a/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart b/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart index cf629eef..fd5e1e3f 100644 --- a/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart +++ b/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart @@ -20,6 +20,23 @@ class _UserTileState extends State { late bool isInsideCamp; bool loading = false; + @override + void initState() { + super.initState(); + isInsideCamp = widget.user.currentAttendance == 'Inside Camp'; + } + + @override + void didUpdateWidget(UserTile oldWidget) { + super.didUpdateWidget(oldWidget); + // Update the state when the user data changes + if (oldWidget.user.currentAttendance != widget.user.currentAttendance) { + setState(() { + isInsideCamp = widget.user.currentAttendance == 'Inside Camp'; + }); + } + } + String inCampStatusTextChanger(bool value) { return value ? "IN CAMP" : "NOT IN CAMP"; } @@ -176,11 +193,23 @@ class _UserTileState extends State { Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: AnimatedToggleSwitch.rolling( - current: widget.user.currentAttendance == 'Inside Camp', + current: isInsideCamp, values: const [false, true], onChanged: (value) async { - final attendanceProvider = Provider.of(context, listen: false); - await attendanceProvider.updateUserAttendanceRecord(widget.user.id, value).first; + setState(() { + loading = true; + }); + try { + final attendanceProvider = Provider.of(context, listen: false); + await attendanceProvider.updateUserAttendanceRecord(widget.user.id, value).first; + setState(() { + isInsideCamp = value; + }); + } finally { + setState(() { + loading = false; + }); + } }, iconBuilder: rollingIconBuilder, borderWidth: 2.w, From 287d435db30eb6c432bb4a3907cebfa1d41052f5 Mon Sep 17 00:00:00 2001 From: Mahesh1772 <87927591+Mahesh1772@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:21:16 +0800 Subject: [PATCH 3/4] Fix Attendance tile UI --- .../presentation/widgets/attendance_tile.dart | 167 ++++++++++-------- .../presentation/widgets/user_tile.dart | 2 +- 2 files changed, 98 insertions(+), 71 deletions(-) diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart index fbb80196..0f0cddfa 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/widgets/attendance_tile.dart @@ -3,15 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../domain/entities/attendance_record.dart'; -import 'package:intl/intl.dart'; class AttendanceTile extends StatelessWidget { final AttendanceRecord record; final Function(AttendanceRecord) onEdit; final Function(String) onDelete; - final DateFormat standardFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); - AttendanceTile({ + const AttendanceTile({ super.key, required this.record, required this.onEdit, @@ -20,80 +18,109 @@ class AttendanceTile extends StatelessWidget { @override Widget build(BuildContext context) { - final bool isInsideCamp = record.isInsideCamp; - final Color tileColor = isInsideCamp ? Colors.green.shade600 : Colors.red; - final IconData tileIcon = isInsideCamp ? Icons.work_history : Icons.home; + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; - return Padding( - padding: EdgeInsets.only(bottom: 8.0.h), - child: Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - SlidableAction( - onPressed: (context) => onEdit(record), - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - icon: Icons.pending_actions_outlined, - label: 'Edit', - ), - SlidableAction( - onPressed: (context) => onDelete(record.id), - backgroundColor: Colors.red, - foregroundColor: Colors.white, - icon: Icons.delete_forever_rounded, - label: 'Delete', + return Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + onPressed: (_) => onEdit(record), + backgroundColor: theme.colorScheme.secondary, + foregroundColor: Colors.white, + icon: Icons.edit_rounded, + label: 'Edit', + ), + SlidableAction( + onPressed: (_) => onDelete(record.id), + backgroundColor: theme.colorScheme.error, + foregroundColor: Colors.white, + icon: Icons.delete_rounded, + label: 'Delete', + borderRadius: BorderRadius.horizontal( + right: Radius.circular(12.r), ), - ], - ), + ), + ], + ), + child: InkWell( + onTap: () => onEdit(record), child: Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.all(12.w), decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.r), - bottomLeft: Radius.circular(12.r), - ), + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : theme.colorScheme.surface, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode ? Colors.black.withOpacity(0.5) : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + spreadRadius: isDarkMode ? 1.r : 0.r, + ), + ], ), - child: Padding( - padding: EdgeInsets.all(16.0.sp), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon( - tileIcon, - color: Colors.white, - size: 30.sp, - ), - SizedBox(width: 20.w), - SizedBox( - width: 100.w, - child: AutoSizeText( - isInsideCamp ? "BOOK IN" : "BOOK OUT", - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - fontSize: 20.sp, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - SizedBox(width: 20.w), - SizedBox( - width: 180.w, - child: AutoSizeText( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( record.dateTime, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - fontSize: 14.sp, - color: Colors.white, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + style: theme.textTheme.titleMedium?.copyWith( + color: isDarkMode + ? Colors.white + : theme.colorScheme.tertiary, + letterSpacing: 1.2, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), ), - ), - ], - ), + SizedBox(height: 4.h), + Text( + record.isInsideCamp ? 'Inside Camp' : 'Outside Camp', + style: theme.textTheme.titleSmall?.copyWith( + color: record.isInsideCamp + ? (isDarkMode ? const Color.fromARGB(255, 130, 100, 255) : theme.colorScheme.secondary) + : (isDarkMode ? const Color.fromARGB(255, 255, 100, 100) : theme.colorScheme.error), + letterSpacing: 1.2, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Row( + children: [ + Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: record.isInsideCamp + ? (isDarkMode ? const Color.fromARGB(255, 130, 100, 255) : theme.colorScheme.secondary) + : (isDarkMode ? const Color.fromARGB(255, 255, 100, 100) : theme.colorScheme.error), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + record.isInsideCamp + ? Icons.check_circle_outline_rounded + : Icons.cancel_outlined, + color: Colors.white, + size: 20.sp, + ), + ), + SizedBox(width: 8.w), + Icon( + Icons.chevron_right_rounded, + color: theme.colorScheme.tertiary.withOpacity(0.5), + size: 24.sp, + ), + ], + ), + ], ), ), ), diff --git a/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart b/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart index fd5e1e3f..eb69a14d 100644 --- a/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart +++ b/trooptrak_final_application/lib/features/nominal_roll/presentation/widgets/user_tile.dart @@ -180,7 +180,7 @@ class _UserTileState extends State { ), SizedBox(height: 8.h), Text( - inCampStatusTextChanger(widget.user.currentAttendance == 'Inside Camp'), + isInsideCamp ? 'Inside Camp' : 'Outside Camp', style: theme.textTheme.bodySmall?.copyWith( color: isDarkMode ? Colors.white70 : Colors.black54, fontSize: 12.sp, From ad9179ce360dc1c0568652d0ca038ff3c068f42d Mon Sep 17 00:00:00 2001 From: Mahesh1772 <87927591+Mahesh1772@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:30:00 +0800 Subject: [PATCH 4/4] Sort attendance tab --- .../detailed_view/presentation/pages/attendance_tab.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart index 56ac661f..bbacc30c 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/attendance_tab.dart @@ -55,7 +55,7 @@ class AttendanceTab extends StatelessWidget { builder: (context, provider, child) { return StreamBuilder>( stream: provider.getUserAttendance(userId), - builder: (context, AsyncSnapshot> snapshot) { + builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } @@ -64,7 +64,7 @@ class AttendanceTab extends StatelessWidget { return const Center(child: Text('No attendance records found')); } - final records = snapshot.data!; + final records = snapshot.data!.toList()..sort((a, b) => b.dateTime.compareTo(a.dateTime)); return ListView.builder( itemCount: records.length, itemBuilder: (context, index) {