diff --git a/campus/frontend/lib/avinya/attendance/lib/app.dart b/campus/frontend/lib/avinya/attendance/lib/app.dart index f197b9b9..d17f4969 100644 --- a/campus/frontend/lib/avinya/attendance/lib/app.dart +++ b/campus/frontend/lib/avinya/attendance/lib/app.dart @@ -50,6 +50,7 @@ class _CampusAttendanceManagementSystemState '/bulk_attendance_marker/class2', '/daily_attendance_report', '/weekly_payment_report', + '/monthly_payment_report', '/avinya_types', '/#access_token', '/person_attendance_report', @@ -128,26 +129,30 @@ class _CampusAttendanceManagementSystemState '/daily_attendance_report', '/daily_attendance_report', {}, {}); final weeklyPaymentReportRoute = ParsedRoute('/weekly_payment_report', '/weekly_payment_report', {}, {}); + final monthlyPaymentReportRoute = ParsedRoute( + '/monthly_payment_report', '/monthly_payment_report', {}, {}); final personAttendanceReportRoute = ParsedRoute( '/person_attendance_report', '/person_attendance_report', {}, {}); final lateAttendanceReportRoute = ParsedRoute( '/late_attendance_report', '/late_attendance_report', {}, {}); - final dutyParticipantsRoute = ParsedRoute( - '/duty_participants','/duty_participants', {}, {}); + final dutyParticipantsRoute = + ParsedRoute('/duty_participants', '/duty_participants', {}, {}); final dutyAttendanceMarkerRoute = ParsedRoute( - '/duty_attendance_marker','/duty_attendance_marker', {}, {}); + '/duty_attendance_marker', '/duty_attendance_marker', {}, {}); final qrAttendanceMarkerRoute = ParsedRoute('/qr_attendance_marker', '/qr_attendance_marker', {}, {}); - - final dailyDutyAttendanceReportRoute = - ParsedRoute('/daily_duty_attendance_report', '/daily_duty_attendance_report', {}, {}); - final dailyAttendanceSummaryReportRoute = - ParsedRoute('/daily_attendance_summary_report', '/daily_attendance_summary_report', {}, {}); + final dailyDutyAttendanceReportRoute = ParsedRoute( + '/daily_duty_attendance_report', + '/daily_duty_attendance_report', {}, {}); + + final dailyAttendanceSummaryReportRoute = ParsedRoute( + '/daily_attendance_summary_report', + '/daily_attendance_summary_report', {}, {}); // // Go to /apply if the user is not signed in log("_guard signed in $signedIn"); @@ -168,13 +173,15 @@ class _CampusAttendanceManagementSystemState return dailyAttendanceReportRoute; } else if (signedIn && from == weeklyPaymentReportRoute) { return weeklyPaymentReportRoute; - } else if (signedIn && from == personAttendanceReportRoute){ + } else if (signedIn && from == weeklyPaymentReportRoute) { + return monthlyPaymentReportRoute; + } else if (signedIn && from == personAttendanceReportRoute) { return personAttendanceReportRoute; - } else if (signedIn && from == dutyParticipantsRoute){ - return dutyParticipantsRoute; - } else if (signedIn && from == dutyAttendanceMarkerRoute){ - return dutyAttendanceMarkerRoute; - }else if (signedIn && from == qrAttendanceMarkerRoute) { + } else if (signedIn && from == dutyParticipantsRoute) { + return dutyParticipantsRoute; + } else if (signedIn && from == dutyAttendanceMarkerRoute) { + return dutyAttendanceMarkerRoute; + } else if (signedIn && from == qrAttendanceMarkerRoute) { } else if (signedIn && from == lateAttendanceReportRoute) { return lateAttendanceReportRoute; } else if (signedIn && from == qrAttendanceMarkerRoute) { diff --git a/campus/frontend/lib/avinya/attendance/lib/screens/monthly_payment_report.dart b/campus/frontend/lib/avinya/attendance/lib/screens/monthly_payment_report.dart new file mode 100644 index 00000000..7aac408a --- /dev/null +++ b/campus/frontend/lib/avinya/attendance/lib/screens/monthly_payment_report.dart @@ -0,0 +1,167 @@ +// Copyright 2019 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:attendance/widgets/excel_export.dart'; +import 'package:gallery/data/person.dart'; +import 'package:attendance/data/activity_attendance.dart'; +import 'package:gallery/avinya/attendance/lib/widgets/monthly_payment_report.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:intl/intl.dart'; + +class MonthlyPaymentReportScreen extends StatefulWidget { + const MonthlyPaymentReportScreen({super.key}); + + @override + _MonthlyPaymentReportScreenState createState() => + _MonthlyPaymentReportScreenState(); +} + +class _MonthlyPaymentReportScreenState extends State + with SingleTickerProviderStateMixin { + List _fetchedExcelReportData = []; + List _fetchedStudentList = []; + List columnNames = []; + var activityId = 0; + bool isFetching = true; + + //calendar specific variables + DateTime _focusedDay = DateTime.now(); + DateTime? _selectedDay; + DateTime? _rangeStart; + DateTime? _rangeEnd; + + late String formattedStartDate; + late String formattedEndDate; + + void selectWeek(DateTime today, activityId) async { + // Calculate the start of the week (excluding weekends) based on the selected day + DateTime startOfWeek = today.subtract(Duration(days: today.weekday - 1)); + while (startOfWeek.weekday > DateTime.friday) { + startOfWeek = startOfWeek.subtract(Duration(days: 1)); + } + + // Calculate the end of the week (excluding weekends) based on the start of the week + DateTime endOfWeek = startOfWeek.add(Duration(days: 4)); + + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization!.id; + + if (parentOrgId != null) { + setState(() { + this.isFetching = true; + }); + try { + _fetchedExcelReportData = + await getClassActivityAttendanceReportByParentOrg( + parentOrgId, + activityId, + DateFormat('yyyy-MM-dd').format(startOfWeek), + DateFormat('yyyy-MM-dd').format(endOfWeek)); + _fetchedStudentList = await fetchStudentList(parentOrgId); + + setState(() { + this._fetchedExcelReportData = _fetchedExcelReportData; + this._fetchedStudentList = _fetchedStudentList; + this.isFetching = false; + }); + } catch (e) { + setState(() { + this.isFetching = false; + }); + } + } + } + + void updateDateRange(_rangeStart, _rangeEnd) async { + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization!.id; + if (parentOrgId != null) { + setState(() { + this.isFetching = true; + }); + try { + _fetchedExcelReportData = + await getClassActivityAttendanceReportByParentOrg( + parentOrgId, + activityId, + DateFormat('yyyy-MM-dd').format(_rangeStart), + DateFormat('yyyy-MM-dd').format(_rangeEnd)); + setState(() { + final startDate = _rangeStart ?? _selectedDay; + final endDate = _rangeEnd ?? _selectedDay; + final formatter = DateFormat('MMM d, yyyy'); + final formattedStartDate = formatter.format(startDate!); + final formattedEndDate = formatter.format(endDate!); + this.formattedStartDate = formattedStartDate; + this.formattedEndDate = formattedEndDate; + this._fetchedStudentList = _fetchedStudentList; + // this.isFetching = false; + this._fetchedExcelReportData = _fetchedExcelReportData; + }); + } catch (e) { + setState(() { + this.isFetching = false; + }); + } + } + } + + @override + void initState() { + super.initState(); + var today = DateTime.now(); + activityId = campusAppsPortalInstance.activityIds['homeroom']!; + selectWeek(today, activityId); + } + + void updateExcelState() { + ExcelExport( + fetchedAttendance: _fetchedExcelReportData, + columnNames: columnNames, + fetchedStudentList: _fetchedStudentList, + updateExcelState: updateExcelState, + isFetching: isFetching, + ); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text("Weekly Student Payment Report", + style: TextStyle(color: Colors.black)), + backgroundColor: Color.fromARGB(255, 236, 230, 253), + ), + body: SingleChildScrollView( + child: Container( + child: Column( + children: [ + MonthlyPaymentReport( + title: 'Weekly Student Payment', + updateDateRangeForExcel: updateDateRange, + ) + ], + ), + ), + ), + floatingActionButton: this.isFetching + ? null + : ExcelExport( + fetchedAttendance: _fetchedExcelReportData, + columnNames: columnNames, + fetchedStudentList: _fetchedStudentList, + updateExcelState: updateExcelState, + isFetching: isFetching, + ), + floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, + ); + } +} diff --git a/campus/frontend/lib/avinya/attendance/lib/screens/scaffold.dart b/campus/frontend/lib/avinya/attendance/lib/screens/scaffold.dart index 4d5015a9..fd935c4e 100644 --- a/campus/frontend/lib/avinya/attendance/lib/screens/scaffold.dart +++ b/campus/frontend/lib/avinya/attendance/lib/screens/scaffold.dart @@ -118,8 +118,8 @@ class _SMSScaffoldState extends State { padding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 5.0), child: ListTile( hoverColor: Colors.white.withOpacity(0.3), - leading: Icon(Icons.summarize_sharp, - color: Colors.white, size: 20.0), + leading: + Icon(Icons.summarize_sharp, color: Colors.white, size: 20.0), title: Container( margin: EdgeInsets.only(left: 12.0), transform: Matrix4.translationValues(-25, 0.0, 0.0), @@ -161,6 +161,29 @@ class _SMSScaffoldState extends State { ), ), ), + // Material( + // type: MaterialType.transparency, + // child: Container( + // padding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 5.0), + // child: ListTile( + // hoverColor: Colors.white.withOpacity(0.3), + // leading: + // Icon(Icons.paid_outlined, color: Colors.white, size: 20.0), + // title: Container( + // margin: EdgeInsets.only(left: 12.0), + // transform: Matrix4.translationValues(-25, 0.0, 0.0), + // child: Text( + // "Weekly Payment Report", + // style: TextStyle(color: Colors.white, fontSize: 12), + // ), + // ), + // onTap: () { + // Navigator.pop(context); // Close the drawer + // routeState.go('/weekly_payment_report'); + // }, + // ), + // ), + // ), Material( type: MaterialType.transparency, child: Container( @@ -173,13 +196,13 @@ class _SMSScaffoldState extends State { margin: EdgeInsets.only(left: 12.0), transform: Matrix4.translationValues(-25, 0.0, 0.0), child: Text( - "Weekly Payment Report", + "Monthly Payment Report", style: TextStyle(color: Colors.white, fontSize: 12), ), ), onTap: () { Navigator.pop(context); // Close the drawer - routeState.go('/weekly_payment_report'); + routeState.go('/monthly_payment_report'); }, ), ), @@ -319,12 +342,16 @@ class _SMSScaffoldState extends State { return Scaffold( appBar: AppBar( - title: Text("Avinya Academy - Campus Attendance Portal",style: TextStyle(color: Colors.white)), + title: Text("Avinya Academy - Campus Attendance Portal", + style: TextStyle(color: Colors.white)), backgroundColor: Colors.deepPurpleAccent, iconTheme: IconThemeData(color: Colors.white), actions: [ IconButton( - icon: const Icon(Icons.logout,color: Colors.white,), + icon: const Icon( + Icons.logout, + color: Colors.white, + ), tooltip: 'Logout', onPressed: () { SMSAuthScope.of(context).signOut(); @@ -333,7 +360,7 @@ class _SMSScaffoldState extends State { }, ), IconButton( - icon: const Icon(Icons.info,color: Colors.white), + icon: const Icon(Icons.info, color: Colors.white), tooltip: 'Help', onPressed: () { Navigator.push(context, MaterialPageRoute( diff --git a/campus/frontend/lib/avinya/attendance/lib/screens/scaffold_body.dart b/campus/frontend/lib/avinya/attendance/lib/screens/scaffold_body.dart index c8c67eda..a3e78946 100644 --- a/campus/frontend/lib/avinya/attendance/lib/screens/scaffold_body.dart +++ b/campus/frontend/lib/avinya/attendance/lib/screens/scaffold_body.dart @@ -14,6 +14,7 @@ import 'package:attendance/screens/duty_attendance_marker.dart'; import 'package:attendance/screens/late_attendance_report.dart'; import 'package:attendance/screens/dashboard/dashboard_screen.dart'; import 'package:attendance/screens/daily_attendance_summary_report.dart'; +import 'package:gallery/avinya/attendance/lib/screens/monthly_payment_report.dart'; import '../routing.dart'; import '../widgets/fade_transition_page.dart'; @@ -83,6 +84,12 @@ class SMSScaffoldBody extends StatelessWidget { key: ValueKey('weekly_payment_report'), child: WeeklyPaymentReportScreen(), ) + else if (currentRoute.pathTemplate + .startsWith('/monthly_payment_report')) + const FadeTransitionPage( + key: ValueKey('monthly_payment_report'), + child: MonthlyPaymentReportScreen(), + ) else if (currentRoute.pathTemplate .startsWith('/person_attendance_report')) const FadeTransitionPage( diff --git a/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_calender.dart b/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_calender.dart new file mode 100644 index 00000000..4b5556e7 --- /dev/null +++ b/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_calender.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; // For formatting the date +import 'package:table_calendar/table_calendar.dart'; + +class LeaveDatePicker extends StatefulWidget { + @override + _LeaveDatePickerState createState() => _LeaveDatePickerState(); +} + +class _LeaveDatePickerState extends State { + // This will hold the selected dates + List _selectedDates = []; + + // Date selection function + void _onDaySelected(DateTime day, DateTime focusedDay) { + setState(() { + // Toggle selection on/off for the selected day + if (_selectedDates.contains(day)) { + _selectedDates.remove(day); + } else { + _selectedDates.add(day); + } + }); + } + + // Function to format date in a more readable way + String _formatDate(DateTime date) { + return DateFormat('MMMM dd, yyyy').format(date); // e.g., October 15, 2024 + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Select Leave Dates"), + ), + body: Column( + children: [ + TableCalendar( + firstDay: DateTime.utc(2020, 1, 1), + lastDay: DateTime.utc(2030, 12, 31), + focusedDay: DateTime.now(), + selectedDayPredicate: (day) { + // Highlight the days that have been selected + return _selectedDates.contains(day); + }, + onDaySelected: _onDaySelected, + calendarStyle: CalendarStyle( + isTodayHighlighted: true, + selectedDecoration: BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + todayDecoration: BoxDecoration( + color: Colors.orange, + shape: BoxShape.circle, + ), + ), + ), + SizedBox(height: 20), + // Display the selected leave dates prominently + Text( + "Selected Leave Dates", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + SizedBox(height: 10), + Expanded( + child: _selectedDates.isNotEmpty + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, // Display 2 items per row + childAspectRatio: 3, // Height-to-width ratio + ), + itemCount: _selectedDates.length, + itemBuilder: (context, index) { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 3, + margin: EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Icon( + Icons.calendar_today, + color: Colors.blueAccent, + size: 30, + ), + SizedBox(width: 10), + Flexible( + child: Text( + _formatDate(_selectedDates[index]), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ) + : Center( + child: Text( + "No dates selected yet.", + style: TextStyle( + fontSize: 16, + fontStyle: FontStyle.italic, + color: Colors.grey, + ), + ), + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Save or use the selected dates as needed + print("Selected Leave Dates: $_selectedDates"); + }, + child: Icon(Icons.save), + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_payment_report.dart b/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_payment_report.dart new file mode 100644 index 00000000..72f14076 --- /dev/null +++ b/campus/frontend/lib/avinya/attendance/lib/widgets/monthly_payment_report.dart @@ -0,0 +1,746 @@ +import 'package:flutter/material.dart'; +import 'package:attendance/widgets/week_picker.dart'; +import 'package:attendance/widgets/excel_export.dart'; +import 'package:gallery/avinya/attendance/lib/widgets/monthly_calender.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:attendance/data/activity_attendance.dart'; +import 'package:gallery/data/person.dart'; +import 'package:intl/intl.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +class MonthlyPaymentReport extends StatefulWidget { + const MonthlyPaymentReport( + {Key? key, required this.title, required this.updateDateRangeForExcel}) + : super(key: key); + final Function(DateTime, DateTime) updateDateRangeForExcel; + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MonthlyPaymentReportState(); +} + +class _MonthlyPaymentReportState extends State { + List _fetchedAttendance = []; + List _fetchedExcelReportData = []; + List _fetchedAttendanceAfterSchool = []; + List _fetchedStudentList = []; + Organization? _fetchedOrganization; + bool _isFetching = true; + + //calendar specific variables + DateTime _focusedDay = DateTime.now(); + DateTime? _selectedDay; + DateTime? _rangeStart; + DateTime? _rangeEnd; + + late DataTableSource _data; + List columnNames = []; + List> attendanceList = []; + var _selectedValue; + var activityId = 0; + + late String formattedStartDate; + late String formattedEndDate; + var today = DateTime.now(); + + void selectWeek(DateTime today, activityId) async { + // Calculate the start of the week (excluding weekends) based on the selected day + DateTime startOfWeek = today.subtract(Duration(days: today.weekday - 1)); + while (startOfWeek.weekday > DateTime.friday) { + startOfWeek = startOfWeek.subtract(Duration(days: 1)); + } + + // Calculate the end of the week (excluding weekends) based on the start of the week + DateTime endOfWeek = startOfWeek.add(Duration(days: 4)); + + // Update the variables to select the week + final formatter = DateFormat('MMM d, yyyy'); + formattedStartDate = formatter.format(startOfWeek); + formattedEndDate = formatter.format(endOfWeek); + + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization!.id; + + if (parentOrgId != null) { + setState(() { + _isFetching = true; // Set _isFetching to true before starting the fetch + }); + + try { + final fetchedExcelReportData = + await getClassActivityAttendanceReportByParentOrg( + parentOrgId, + activityId, + DateFormat('yyyy-MM-dd').format(startOfWeek), + DateFormat('yyyy-MM-dd').format(endOfWeek), + ); + final fetchedStudentList = await fetchStudentList(parentOrgId); + + setState(() { + _fetchedExcelReportData = fetchedExcelReportData; + _fetchedStudentList = fetchedStudentList; + _isFetching = + false; // Set _isFetching to false after the fetch completes + }); + } catch (error) { + // Handle any errors that occur during the fetch + setState(() { + _isFetching = false; // Set _isFetching to false in case of error + }); + // Perform error handling, e.g., show an error message + } + } + } + + @override + void initState() { + super.initState(); + var today = DateTime.now(); + activityId = campusAppsPortalInstance.activityIds['homeroom']!; + selectWeek(today, activityId); + } + + void updateExcelState() { + ExcelExport( + fetchedAttendance: _fetchedExcelReportData, + columnNames: columnNames, + fetchedStudentList: _fetchedStudentList, + updateExcelState: updateExcelState, + isFetching: _isFetching, + ); + } + + @override + void didChangeDependencies() async { + super.didChangeDependencies(); + _data = MyData( + _fetchedAttendance, columnNames, _fetchedOrganization, updateSelected); + WeekPicker(updateDateRange, formattedStartDate); + } + + void updateSelected(int index, bool value, List selected) { + setState(() { + selected[index] = value; + }); + } + + void updateDateRange(_rangeStart, _rangeEnd) async { + widget.updateDateRangeForExcel(_rangeStart, _rangeEnd); + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization!.id; + if (_fetchedOrganization != null) { + _fetchedAttendance = await getClassActivityAttendanceReportForPayment( + this._fetchedOrganization!.id!, + activityId, + DateFormat('yyyy-MM-dd').format(_rangeStart), + DateFormat('yyyy-MM-dd').format(_rangeEnd)); + } + if (parentOrgId != null) { + setState(() { + _isFetching = true; // Set _isFetching to true before starting the fetch + }); + try { + _fetchedExcelReportData = + await getClassActivityAttendanceReportByParentOrg( + parentOrgId, + activityId, + DateFormat('yyyy-MM-dd').format(_rangeStart), + DateFormat('yyyy-MM-dd').format(_rangeEnd)); + final fetchedStudentList = await fetchStudentList(parentOrgId); + setState(() { + final startDate = _rangeStart ?? _selectedDay; + final endDate = _rangeEnd ?? _selectedDay; + final formatter = DateFormat('MMM d, yyyy'); + final formattedStartDate = formatter.format(startDate!); + final formattedEndDate = formatter.format(endDate!); + this.formattedStartDate = formattedStartDate; + this.formattedEndDate = formattedEndDate; + this._fetchedStudentList = fetchedStudentList; + this._fetchedExcelReportData = _fetchedExcelReportData; + _isFetching = false; + if (this._selectedValue != null) { + refreshState(this._selectedValue); + } + }); + } catch (error) { + // Handle any errors that occur during the fetch + setState(() { + _isFetching = false; // Set _isFetching to false in case of error + }); + // Perform error handling, e.g., show an error message + } + } + } + + void refreshState(Organization? newValue) async { + setState(() { + _isFetching = true; // Set _isFetching to true before starting the fetch + }); + var cols = + columnNames.map((label) => DataColumn(label: Text(label!))).toList(); + _selectedValue = newValue!; + // print(newValue.id); + _fetchedOrganization = await fetchOrganization(newValue.id!); + + _fetchedAttendance = await getClassActivityAttendanceReportForPayment( + _fetchedOrganization!.id!, + activityId, + DateFormat('yyyy-MM-dd') + .format(DateFormat('MMM d, yyyy').parse(this.formattedStartDate)), + DateFormat('yyyy-MM-dd') + .format(DateFormat('MMM d, yyyy').parse(this.formattedEndDate))); + if (_fetchedAttendance.length > 0) { + // Add null check here + // Process attendance data here + columnNames.clear(); + List names = _fetchedAttendance + .map((attendance) => attendance.sign_in_time?.split(" ")[0]) + .where((name) => name != null) // Filter out null values + .toList(); + columnNames.addAll(names); + } else { + columnNames.clear(); + } + + columnNames = columnNames.toSet().toList(); + columnNames.sort(); + columnNames.insert(0, "Name"); + columnNames.insert(1, "Digital ID"); + columnNames.insert(columnNames.length, "Present Count"); + columnNames.insert(columnNames.length, "Absent Count"); + columnNames.insert(columnNames.length, "Student Payment Rs."); + columnNames.insert(columnNames.length, "Phone Payment Rs."); + cols = columnNames.map((label) => DataColumn(label: Text(label!))).toList(); + print(cols.length); + if (_fetchedAttendance.length == 0) + _fetchedAttendance = new List.filled(_fetchedOrganization!.people.length, + new ActivityAttendance(person_id: -1)); + else { + for (int i = 0; i < _fetchedOrganization!.people.length; i++) { + if (_fetchedAttendance.indexWhere((attendance) => + attendance.person_id == _fetchedOrganization!.people[i].id) == + -1) { + _fetchedAttendance.add(new ActivityAttendance(person_id: -1)); + } + } + } + + setState(() { + _fetchedOrganization; + this._isFetching = false; + _data = MyData(_fetchedAttendance, columnNames, _fetchedOrganization, + updateSelected); + }); + } + + @override + Widget build(BuildContext context) { + var cols = + columnNames.map((label) => DataColumn(label: Text(label!))).toList(); + + ExcelExport( + fetchedAttendance: _fetchedExcelReportData, + columnNames: columnNames, + fetchedStudentList: _fetchedStudentList, + updateExcelState: updateExcelState, + isFetching: _isFetching, + ); + + return SingleChildScrollView( + child: campusAppsPortalPersonMetaDataInstance + .getGroups() + .contains('Student') + ? Text("Please go to 'Mark Attedance' Page", + style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold)) + : Wrap( + children: [ + Row( + children: [ + SizedBox(width: 20), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + for (var org in campusAppsPortalInstance + .getUserPerson() + .organization! + .child_organizations) + // create a text widget with some padding + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (org.child_organizations.length > 0) + Container( + margin: EdgeInsets.only( + left: 20, top: 20, bottom: 10), + child: Row(children: [ + Text('Select a class:'), + SizedBox(width: 10), + DropdownButton( + value: _selectedValue, + onChanged: + (Organization? newValue) async { + _selectedValue = newValue!; + print(newValue.id); + _fetchedOrganization = + await fetchOrganization( + newValue.id!); + + _fetchedAttendance = + await getClassActivityAttendanceReportForPayment( + _fetchedOrganization!.id!, + activityId, + DateFormat('yyyy-MM-dd') + .format(DateFormat( + 'MMM d, yyyy') + .parse(this + .formattedStartDate)), + DateFormat('yyyy-MM-dd') + .format(DateFormat( + 'MMM d, yyyy') + .parse(this + .formattedEndDate))); + if (_fetchedAttendance.length > 0) { + // Add null check here + // Process attendance data here + columnNames.clear(); + List names = + _fetchedAttendance + .map((attendance) => + attendance.sign_in_time + ?.split(" ")[0]) + .where((name) => + name != + null) // Filter out null values + .toList(); + columnNames.addAll(names); + } else { + columnNames.clear(); + } + + columnNames = + columnNames.toSet().toList(); + columnNames.sort(); + columnNames.insert(0, "Name"); + columnNames.insert(1, "Digital ID"); + columnNames.insert(columnNames.length, + "Present Count"); + columnNames.insert(columnNames.length, + "Absent Count"); + columnNames.insert(columnNames.length, + "Student Payment Rs."); + columnNames.insert(columnNames.length, + "Phone Payment Rs."); + cols = columnNames + .map((label) => DataColumn( + label: Text(label!))) + .toList(); + print(cols.length); + if (_fetchedAttendance.length == 0) + _fetchedAttendance = + new List.filled( + _fetchedOrganization! + .people.length, + new ActivityAttendance( + person_id: -1)); + else { + for (int i = 0; + i < + _fetchedOrganization! + .people.length; + i++) { + if (_fetchedAttendance.indexWhere( + (attendance) => + attendance + .person_id == + _fetchedOrganization! + .people[i].id) == + -1) { + _fetchedAttendance.add( + new ActivityAttendance( + person_id: -1)); + } + } + } + setState(() { + _fetchedOrganization; + _data = MyData( + _fetchedAttendance, + columnNames, + _fetchedOrganization, + updateSelected); + }); + }, + items: org.child_organizations + .map((Organization value) { + return DropdownMenuItem( + value: value, + child: Text(value.description!), + ); + }).toList(), + ), + ]), + ), + ]), + ], + ), + SizedBox(width: 20), + Expanded( + child: Container( + alignment: Alignment.bottomRight, + margin: const EdgeInsets.only(right: 20.0), + width: 25.0, + height: 30.0, + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LeaveDatePicker(), + ), + ); + }, + child: const Text('Create New'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 5.0), + textStyle: const TextStyle(fontSize: 16), + ), + ), + ), + ), + ElevatedButton( + style: ButtonStyle( + textStyle: MaterialStateProperty.all( + TextStyle(fontSize: 20), + ), + elevation: MaterialStateProperty.all(20), + backgroundColor: + MaterialStateProperty.all(Colors.greenAccent), + foregroundColor: + MaterialStateProperty.all(Colors.black), + ), + onPressed: _isFetching + ? null + : () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => WeekPicker( + updateDateRange, formattedStartDate)), + ), + child: Container( + height: 50, // Adjust the height as needed + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_isFetching) + Padding( + padding: EdgeInsets.only(right: 10), + child: SpinKitFadingCircle( + color: Colors + .black, // Customize the color of the indicator + size: + 20, // Customize the size of the indicator + ), + ), + if (!_isFetching) + Icon(Icons.calendar_today, color: Colors.black), + SizedBox(width: 10), + Text( + '${this.formattedStartDate} - ${this.formattedEndDate}', + style: TextStyle( + color: Colors.black, + fontSize: 17, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + SizedBox(width: 20), + ], + ), + SizedBox(height: 16.0), + SizedBox(height: 32.0), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (_isFetching) + Container( + margin: EdgeInsets.only(top: 180), + child: SpinKitCircle( + color: (Colors + .deepPurpleAccent), // Customize the color of the indicator + size: 50, // Customize the size of the indicator + ), + ) + else if (cols.length > 2) + PaginatedDataTable( + showCheckboxColumn: false, + source: _data, + columns: cols, + // header: const Center(child: Text('Daily Attendance')), + columnSpacing: 100, + horizontalMargin: 60, + rowsPerPage: 22, + ) + else + Container( + margin: EdgeInsets.all(20), + child: Text('No attendance data found'), + ), + ], + ) + ], + ), + ); + } +} + +class MyData extends DataTableSource { + MyData(this._fetchedAttendance, this.columnNames, this._fetchedOrganization, + this.updateSelected); + + final List _fetchedAttendance; + final List columnNames; + final Organization? _fetchedOrganization; + final Function(int, bool, List) updateSelected; + + List getDatesFromMondayToToday() { + DateTime now = DateTime.now(); + DateTime previousMonday = now.subtract(Duration(days: now.weekday - 1)); + DateTime currentDate = DateTime(now.year, now.month, now.day); + + List dates = []; + for (DateTime date = previousMonday; + date.isBefore(currentDate); + date = date.add(Duration(days: 1))) { + if (date.weekday != DateTime.saturday && + date.weekday != DateTime.sunday) { + dates.add(DateFormat('yyyy-MM-dd').format(date)); + } + } + + return dates; + } + + @override + DataRow? getRow(int index) { + if (index == 0) { + List cells = new List.filled( + columnNames.toSet().toList().length, + new DataCell(Container(child: Text("Absent"), color: Colors.red)), + ); + cells[0] = DataCell(Text('')); + cells[1] = DataCell(Text('')); + cells[columnNames.length - 4] = DataCell(Text('')); + cells[columnNames.length - 3] = DataCell(Text('')); + cells[columnNames.length - 2] = DataCell(Text('')); + cells[columnNames.length - 1] = DataCell(Text('')); + + for (final date in columnNames) { + if (columnNames.indexOf(date) == 0 || + columnNames.indexOf(date) == 1 || + columnNames.indexOf(date) == columnNames.length - 4 || + columnNames.indexOf(date) == columnNames.length - 3 || + columnNames.indexOf(date) == columnNames.length - 2 || + columnNames.indexOf(date) == columnNames.length - 1) { + continue; + } + + date == '$date 00:00:00'; + cells[columnNames.indexOf(date)] = DataCell(Container( + alignment: Alignment.center, + padding: EdgeInsets.all(8), + child: Text(DateFormat.EEEE().format(DateTime.parse(date!)), + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + )), + )); + } + return DataRow( + cells: cells, + ); + } + if (_fetchedOrganization != null && + _fetchedOrganization!.people.isNotEmpty && + columnNames.length > 0) { + var person = _fetchedOrganization! + .people[index - 1]; // to facilitate additional rows + List cells = new List.filled( + columnNames.toSet().toList().length, + new DataCell(Container( + alignment: Alignment.center, + child: Text("Absent", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.red, + )))), + ); + + cells[0] = DataCell(Text(person.preferred_name!)); + cells[1] = DataCell(Text(person.digital_id.toString())); + + int absentCount = 0; + + final dateRegex = RegExp(r'^\d{4}-\d{2}-\d{2}$'); + final dateFormatter = DateFormat('yyyy-MM-dd'); + + for (var element in columnNames) { + if (dateRegex.hasMatch(element!)) { + try { + dateFormatter.parseStrict(element); + absentCount++; + } catch (e) { + // Handle the exception or continue to the next element + } + } + } + cells[columnNames.length - 4] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + '0'))); + + cells[columnNames.length - 3] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + absentCount.toString()))); + + cells[columnNames.length - 2] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + '0'))); + + cells[columnNames.length - 1] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + '0'))); + + int presentCount = 0; + for (final attendance in _fetchedAttendance) { + if (attendance.person_id == person.id) { + int newAbsentCount = 0; + for (final date in columnNames) { + if (attendance.sign_in_time != null && + attendance.sign_in_time!.split(" ")[0] == date) { + presentCount++; + // print( + // 'index ${index} date ${date} person_id ${attendance.person_id} sign_in_time ${attendance.sign_in_time} columnNames length ${columnNames.length} columnNames.indexOf(date) ${columnNames.indexOf(date)}'); + cells[columnNames.indexOf(date)] = DataCell(Container( + alignment: Alignment.center, child: Text("Present"))); + } + } + + newAbsentCount = absentCount - presentCount; + cells[columnNames.length - 4] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + presentCount.toString()))); + cells[columnNames.length - 3] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + newAbsentCount.toString()))); + int studentPayment = 100 * presentCount; + cells[columnNames.length - 2] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + studentPayment.toDouble().toStringAsFixed(2)))); + cells[columnNames.length - 1] = DataCell(Container( + alignment: Alignment.center, + child: Text( + style: TextStyle( + color: Color.fromARGB(255, 14, 72, 90), + fontSize: 16, + fontWeight: FontWeight.bold, + ), + studentPayment.toDouble().toStringAsFixed(2)))); + } + } + + int numItems = _fetchedOrganization!.people.length; + List selected = List.generate(numItems, (int index) => false); + return DataRow( + cells: cells, + onSelectChanged: (value) { + updateSelected(index, value!, + selected); // Call the callback to update the selected state + }, + color: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.hovered)) { + return Colors.grey.withOpacity(0.4); + } + if (index.isEven) { + return Colors.grey.withOpacity(0.2); + } + return null; + }), + ); + } + return null; + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount { + int count = 0; + if (_fetchedOrganization != null) { + count = _fetchedOrganization?.people.length ?? 0; + count += 1; //to facilitate additional rows + } + return count; + } + + @override + int get selectedRowCount => 0; +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart index d5e592ee..a14ef06e 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart @@ -146,19 +146,23 @@ class ParentOrganization { class City { int? id; Name? name; + District? district; - City({this.id, this.name}); + City({this.id, this.name, this.district}); factory City.fromJson(Map json) { return City( id: json['id'], - name: Name.fromJson(json['name']), + name: json['name'] != null ? Name.fromJson(json['name']) : null, + district: + json['district'] != null ? District.fromJson(json['district']) : null, ); } Map toJson() => { if (id != null) 'id': id, if (name != null) 'name': name?.toJson(), + if (district != null) 'district': district?.toJson(), }; } @@ -393,18 +397,15 @@ class District { factory District.fromJson(Map json) { return District( id: json['id'], - province: Province.fromJson(json['province']), - cities: - (json['cities'] as List).map((city) => City.fromJson(city)).toList(), - name: Name.fromJson(json['name']), + province: + json['province'] != null ? Province.fromJson(json['province']) : null, + name: json['name'] != null ? Name.fromJson(json['name']) : null, ); } Map toJson() => { if (id != null) 'id': id, if (province != null) 'province': province?.toJson(), - if (cities != null) - 'cities': cities!.map((city) => city.toJson()).toList(), if (name != null) 'name': name?.toJson(), }; } @@ -616,6 +617,25 @@ Future> fetchDistricts() async { .toList(); return activityAttendances; } else { - throw Exception('Failed to get Org Data'); + throw Exception('Failed to get District Data'); + } +} + +Future> fetchCities(id) async { + final response = await http.get( + Uri.parse('${AppConfig.campusEnrollmentsBffApiUrl}/cities/$id'), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'accept': 'application/json', + 'Authorization': 'Bearer ${AppConfig.campusBffApiKey}', + }, + ); + if (response.statusCode > 199 && response.statusCode < 300) { + var resultsJson = json.decode(response.body).cast>(); + List activityAttendances = + await resultsJson.map((json) => City.fromJson(json)).toList(); + return activityAttendances; + } else { + throw Exception('Failed to get City Data'); } } diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart index fc5300d2..2da852b3 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart @@ -18,6 +18,7 @@ class _StudentCreateState extends State { List avinyaTypes = []; List classes = []; final GlobalKey _formKey = GlobalKey(); + List cityList = []; String? selectedSex; int? selectedCityId; @@ -479,6 +480,14 @@ class _StudentCreateState extends State { ); } + Future _loadCities(int? districtId) async { + final fetchedCities = await fetchCities(districtId); + setState(() { + cityList = fetchedCities; + selectedCityId = null; // Reset selected city + }); + } + Widget _buildDistrictField() { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -496,7 +505,8 @@ class _StudentCreateState extends State { child: DropdownButtonFormField( value: selectedDistrictId, items: _getDistrictOptions(), - onChanged: (value) { + onChanged: (value) async { + await _loadCities(value); setState(() { selectedDistrictId = value; userPerson.mailing_address?.district_id = value; @@ -514,18 +524,6 @@ class _StudentCreateState extends State { } Widget _buildCityField() { - List cityList = districts - .firstWhere( - (district) => district.id == selectedDistrictId, - orElse: () => District( - id: 0, - name: Name(name_en: 'Unknown'), - cities: [], - ), - ) - .cities ?? - []; - // Ensure selectedCityId is valid or set to null if not found in the current city's list if (cityList.isNotEmpty && selectedCityId != null) { bool cityExists = cityList.any((city) => city.id == selectedCityId); @@ -603,7 +601,8 @@ class _StudentCreateState extends State { Expanded( flex: 6, child: DropdownButtonFormField( - value: userPerson.organization?.parent_organizations?.first.id, + value: userPerson.organization?.parent_organizations?.first.id ?? + userPerson.organization_id, items: [ DropdownMenuItem( value: null, // Default item for when no selection is made diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart index b68ee88e..9e5499e2 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -18,6 +18,7 @@ class _StudentUpdateState extends State { List avinyaTypes = []; List classes = []; final GlobalKey _formKey = GlobalKey(); + List cityList = []; String? selectedSex; int? selectedCityId; @@ -52,10 +53,14 @@ class _StudentUpdateState extends State { userPerson = user; selectedSex = userPerson.sex; userPerson.avinya_type_id = user.avinya_type_id; - + selectedDistrictId = user.mailing_address?.city?.district!.id; // Safely assign city and organization IDs with fallbacks selectedCityId = userPerson.mailing_address?.city?.id ?? 0; // Default to 0 or another fallback value + if (selectedDistrictId != null) { + _loadCities(selectedDistrictId, selectedCityId); + } + selectedOrgId = userPerson.organization?.id ?? 0; // Similarly handle organization ID selectedClassId = userPerson.organization?.id ?? @@ -117,20 +122,20 @@ class _StudentUpdateState extends State { } } - int? getDistrictIdByCityId(int? selectedCityId, List districtList) { - for (var district in districtList) { - if (district.cities != null) { - // Check if cities is not null - for (var city in district.cities!) { - // Use the non-null assertion operator - if (city.id == selectedCityId) { - return district.id; // Return the district ID if the city ID matches - } - } - } - } - return null; // Return null if no matching district is found - } + // int? getDistrictIdByCityId(int? selectedCityId, List districtList) { + // for (var district in districtList) { + // if (district.cities != null) { + // // Check if cities is not null + // for (var city in district.cities!) { + // // Use the non-null assertion operator + // if (city.id == selectedCityId) { + // return district.id; // Return the district ID if the city ID matches + // } + // } + // } + // } + // return null; // Return null if no matching district is found + // } @override Widget build(BuildContext context) { @@ -267,10 +272,6 @@ class _StudentUpdateState extends State { } }); districts = snapshot.data!; - int? districtId = getDistrictIdByCityId( - selectedCityId, districts); - selectedDistrictId = - districtId ?? selectedDistrictId; return Column( children: [ _buildDistrictField(), @@ -521,6 +522,14 @@ class _StudentUpdateState extends State { ); } + Future _loadCities(int? districtId, int? cityid) async { + final fetchedCities = await fetchCities(districtId); + setState(() { + cityList = fetchedCities; + selectedCityId = cityid; + }); + } + Widget _buildDistrictField() { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -538,7 +547,8 @@ class _StudentUpdateState extends State { child: DropdownButtonFormField( value: selectedDistrictId, items: _getDistrictOptions(), - onChanged: (value) { + onChanged: (value) async { + await _loadCities(value, null); setState(() { selectedDistrictId = value; userPerson.mailing_address?.district_id = value; @@ -556,26 +566,6 @@ class _StudentUpdateState extends State { } Widget _buildCityField() { - List cityList = districts - .firstWhere( - (district) => district.id == selectedDistrictId, - orElse: () => District( - id: 0, - name: Name(name_en: 'Unknown'), - cities: [], - ), - ) - .cities ?? - []; - - // Ensure selectedCityId is valid or set to null if not found in the current city's list - if (cityList.isNotEmpty && selectedCityId != null) { - bool cityExists = cityList.any((city) => city.id == selectedCityId); - if (!cityExists) { - selectedCityId = null; - } - } - return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( @@ -774,11 +764,6 @@ class _StudentUpdateState extends State { ); } - String _formatDate(String? date) { - if (date == null) return 'N/A'; - return DateFormat('d MMM, yyyy').format(DateTime.parse(date)); - } - List> _getDistrictOptions() { return districts.map((district) { return DropdownMenuItem( @@ -788,24 +773,6 @@ class _StudentUpdateState extends State { }).toList(); } - List> _getCityOptions() { - if (selectedDistrictId != null) { - final selectedDistrict = - districts.firstWhere((district) => district.id == selectedDistrictId); - - List? cities = selectedDistrict.cities; - - return cities!.map((city) { - return DropdownMenuItem( - value: city.id as int, // Cast city ID to int - child: Text(city.name?.name_en as String), // Display the English name - ); - }).toList(); - } else { - return []; - } - } - List> _getClassOptions() { return classes .map((classe) => DropdownMenuItem(