diff --git a/campus/bffs/enrollment/api/client.bal b/campus/bffs/enrollment/api/client.bal index e93a8181..c20529a1 100644 --- a/campus/bffs/enrollment/api/client.bal +++ b/campus/bffs/enrollment/api/client.bal @@ -35,7 +35,7 @@ public isolated client class GraphqlClient { return check performDataBinding(graphqlResponse, GetPersonsResponse); } remote isolated function getPersonById(int id) returns GetPersonByIdResponse|graphql:ClientError { - string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email mailing_address {city {id name {name_en name_si name_ta} district {id name {name_en}}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"id": id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); @@ -47,11 +47,17 @@ public isolated client class GraphqlClient { return check performDataBinding(graphqlResponse, UpdatePersonResponse); } remote isolated function getDistricts() returns GetDistrictsResponse|graphql:ClientError { - string query = string `query getDistricts {districts {id province {id name {name_en}} name {name_en} cities {id name {name_en}}}}`; + string query = string `query getDistricts {districts {id province {id name {name_en}} name {name_en}}}`; map variables = {}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetDistrictsResponse); } + remote isolated function getCities(int district_id) returns GetCitiesResponse|graphql:ClientError { + string query = string `query getCities($district_id:Int!) {cities(district_id:$district_id) {id name {name_en}}}`; + map variables = {"district_id": district_id}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, GetCitiesResponse); + } remote isolated function getAllOrganizations() returns GetAllOrganizationsResponse|graphql:ClientError { string query = string `query getAllOrganizations {all_organizations {id name {name_en} address {id street_address} avinya_type {id name} description phone notes}}`; map variables = {}; diff --git a/campus/bffs/enrollment/api/service.bal b/campus/bffs/enrollment/api/service.bal index 00b8494a..c376358a 100644 --- a/campus/bffs/enrollment/api/service.bal +++ b/campus/bffs/enrollment/api/service.bal @@ -100,6 +100,30 @@ service / on new http:Listener(9095) { } } + resource function get cities/[int district_id]() returns City[]|error { + GetCitiesResponse|graphql:ClientError getCitiesResponse = globalDataClient->getCities(district_id); + if (getCitiesResponse is GetCitiesResponse) { + City[] citiesData = []; + foreach var city in getCitiesResponse.cities { + City|error cityData = city.cloneWithType(City); + if (cityData is City) { + citiesData.push(cityData); + } else { + log:printError("Error while processing Application record received", cityData); + return error("Error while processing Application record received: " + cityData.message() + + ":: Detail: " + cityData.detail().toString()); + } + } + + return citiesData; + + } else { + log:printError("Error while getting application", getCitiesResponse); + return error("Error while getting application: " + getCitiesResponse.message() + + ":: Detail: " + getCitiesResponse.detail().toString()); + } + } + resource function get all_organizations() returns Organization[]|error { GetAllOrganizationsResponse|graphql:ClientError getAllOrganizationsResponse = globalDataClient->getAllOrganizations(); if (getAllOrganizationsResponse is GetAllOrganizationsResponse) { diff --git a/campus/bffs/enrollment/api/types.bal b/campus/bffs/enrollment/api/types.bal index f1ab4106..0ab9fc86 100644 --- a/campus/bffs/enrollment/api/types.bal +++ b/campus/bffs/enrollment/api/types.bal @@ -217,19 +217,12 @@ public type GetPersonByIdResponse record {| string? name_si; string? name_ta; |} name; - |} city; - string? street_address; - int? phone; - int? id; - |}? permanent_address; - record {| - record {| - int? id; record {| - string? name_en; - string? name_si; - string? name_ta; - |} name; + int? id; + record {| + string? name_en; + |} name; + |} district; |} city; string? street_address; int? phone; @@ -371,15 +364,18 @@ public type GetDistrictsResponse record {| record {| string? name_en; |} name; - record {| - int? id; - record {| - string? name_en; - |} name; - |}[] cities; |}[] districts; |}; +public type GetCitiesResponse record {| + map __extensions?; + record {| + int? id; + record {| + string? name_en; + |} name; + |}[] cities; +|}; public type GetAvinyaTypesResponse record {| map __extensions?; record {| diff --git a/campus/bffs/enrollment/graphql_client/enrollment.graphql b/campus/bffs/enrollment/graphql_client/enrollment.graphql index 60a7e838..de66aee4 100644 --- a/campus/bffs/enrollment/graphql_client/enrollment.graphql +++ b/campus/bffs/enrollment/graphql_client/enrollment.graphql @@ -90,19 +90,6 @@ query getPersonById($id: Int!) { created updated jwt_email - permanent_address { - city { - id - name { - name_en - name_si - name_ta - } - } - street_address - phone - id - } mailing_address { city { id @@ -111,6 +98,12 @@ query getPersonById($id: Int!) { name_si name_ta } + district { + id + name { + name_en + } + } } street_address phone @@ -158,11 +151,20 @@ query getPersonById($id: Int!) { } } -mutation updatePerson($person: Person!,$permanent_address: Address,$permanent_address_city: City, - $mailing_address: Address,$mailing_address_city: City) { - update_person(person: $person,permanent_address: $permanent_address, - permanent_address_city: $permanent_address_city,mailing_address: $mailing_address, - mailing_address_city: $mailing_address_city) { +mutation updatePerson( + $person: Person! + $permanent_address: Address + $permanent_address_city: City + $mailing_address: Address + $mailing_address_city: City +) { + update_person( + person: $person + permanent_address: $permanent_address + permanent_address_city: $permanent_address_city + mailing_address: $mailing_address + mailing_address_city: $mailing_address_city + ) { id preferred_name full_name @@ -241,62 +243,72 @@ mutation updatePerson($person: Person!,$permanent_address: Address,$permanent_ad } } - query getDistricts { - districts { - id - province{ - id - name{ - name_en - } - } - name{ - name_en - } - cities{ - id - name{ - name_en - } - } + districts { + id + province { + id + name { + name_en + } } + name { + name_en + } + } +} + +query getCities($district_id: Int!) { + cities(district_id: $district_id) { + id + name { + name_en + } + } } query getAvinyaTypes { - avinya_types { - id - active - name - global_type - foundation_type - focus - level - } + avinya_types { + id + active + name + global_type + foundation_type + focus + level + } } query getAllOrganizations { - all_organizations { - id - name{ - name_en - } - address{ - id - street_address - } - avinya_type{ - id - name - } - description - phone - notes + all_organizations { + id + name { + name_en + } + address { + id + street_address } + avinya_type { + id + name + } + description + phone + notes + } } -mutation insertPerson($person: Person!,$mailing_address: Address,$mailing_address_city: City) { - insert_person(person: $person,mailing_address: $mailing_address,mailing_address_city: $mailing_address_city) { +mutation insertPerson( + $person: Person! + $mailing_address: Address + $mailing_address_city: City +) { + insert_person( + person: $person + mailing_address: $mailing_address + mailing_address_city: $mailing_address_city + ) { id preferred_name full_name @@ -374,4 +386,3 @@ mutation insertPerson($person: Person!,$mailing_address: Address,$mailing_addres current_job } } - diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal index 103d701a..b9ffc3be 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal @@ -34,7 +34,7 @@ public isolated client class GraphqlClient { return check performDataBinding(graphqlResponse, GetPersonsResponse); } remote isolated function getPersonById(int id) returns GetPersonByIdResponse|graphql:ClientError { - string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email mailing_address {city {id name {name_en name_si name_ta} district {id name {name_en}}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"id": id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); @@ -46,11 +46,17 @@ public isolated client class GraphqlClient { return check performDataBinding(graphqlResponse, UpdatePersonResponse); } remote isolated function getDistricts() returns GetDistrictsResponse|graphql:ClientError { - string query = string `query getDistricts {districts {id province {id name {name_en}} name {name_en} cities {id name {name_en}}}}`; + string query = string `query getDistricts {districts {id province {id name {name_en}} name {name_en}}}`; map variables = {}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetDistrictsResponse); } + remote isolated function getCities(int district_id) returns GetCitiesResponse|graphql:ClientError { + string query = string `query getCities($district_id:Int!) {cities(district_id:$district_id) {id name {name_en}}}`; + map variables = {"district_id": district_id}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, GetCitiesResponse); + } remote isolated function getAvinyaTypes() returns GetAvinyaTypesResponse|graphql:ClientError { string query = string `query getAvinyaTypes {avinya_types {id active name global_type foundation_type focus level}}`; map variables = {}; diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal index 14671357..c0a7539e 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal @@ -529,19 +529,12 @@ public type GetPersonByIdResponse record {| string? name_si; string? name_ta; |} name; - |} city; - string? street_address; - int? phone; - int? id; - |}? permanent_address; - record {| - record {| - int? id; record {| - string? name_en; - string? name_si; - string? name_ta; - |} name; + int? id; + record {| + string? name_en; + |} name; + |} district; |} city; string? street_address; int? phone; @@ -683,15 +676,19 @@ public type GetDistrictsResponse record {| record {| string? name_en; |} name; - record {| - int? id; - record {| - string? name_en; - |} name; - |}[] cities; |}[]? districts; |}; +public type GetCitiesResponse record {| + map __extensions?; + record {| + int? id; + record {| + string? name_en; + |} name; + |}[]? cities; +|}; + public type GetAvinyaTypesResponse record {| map __extensions?; record {| diff --git a/campus/bffs/enrollment/graphql_client/schema.graphql b/campus/bffs/enrollment/graphql_client/schema.graphql index b3f3ef83..06ef54a0 100644 --- a/campus/bffs/enrollment/graphql_client/schema.graphql +++ b/campus/bffs/enrollment/graphql_client/schema.graphql @@ -930,6 +930,7 @@ type Query { persons(organization_id: Int, avinya_type_id: Int): [PersonData!] person_by_id(id: Int): PersonData districts: [DistrictData!] + cities(district_id: Int): [CityData!] all_organizations: [OrganizationData!] } diff --git a/campus/bffs/enrollment/graphql_client/schema.json b/campus/bffs/enrollment/graphql_client/schema.json index 29facc76..1479556a 100644 --- a/campus/bffs/enrollment/graphql_client/schema.json +++ b/campus/bffs/enrollment/graphql_client/schema.json @@ -3066,6 +3066,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "cities", + "args": [ + { + "name": "district_id", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CityData", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "all_organizations", "args": [], 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(