From c7890e77f2d774b6ac401e7ce17b4a37a15ef8b2 Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Mon, 23 Sep 2024 20:17:04 +0530 Subject: [PATCH 1/6] Students Enrolment interfaces WIP --- campus/bffs/attendance/api/Dependencies.toml | 69 ++- campus/frontend/.fvmrc | 3 + campus/frontend/.gitignore | 3 + campus/frontend/.vscode/settings.json | 11 +- .../lib/screens/students_screen.dart | 19 +- ...ttendance_summary_excel_report_export.dart | 178 +++++++ .../enrollment/lib/widgets/students.dart | 467 +++++++++++++++++- .../ephemeral/Flutter-Generated.xcconfig | 4 +- .../ephemeral/flutter_export_environment.sh | 4 +- 9 files changed, 723 insertions(+), 35 deletions(-) create mode 100644 campus/frontend/.fvmrc create mode 100644 campus/frontend/.gitignore create mode 100644 campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart diff --git a/campus/bffs/attendance/api/Dependencies.toml b/campus/bffs/attendance/api/Dependencies.toml index 2ac8782e..6085c556 100644 --- a/campus/bffs/attendance/api/Dependencies.toml +++ b/campus/bffs/attendance/api/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.5.0" +distribution-version = "2201.8.7" [[package]] org = "avinyafoundation" @@ -26,7 +26,7 @@ modules = [ [[package]] org = "ballerina" name = "auth" -version = "2.8.0" +version = "2.10.0" dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "jballerina.java"}, @@ -38,7 +38,7 @@ dependencies = [ [[package]] org = "ballerina" name = "cache" -version = "3.5.0" +version = "3.8.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "jballerina.java"}, @@ -49,7 +49,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.2.0" +version = "1.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] @@ -57,7 +57,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.3.2" +version = "2.6.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "file" -version = "1.7.1" +version = "1.9.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -77,25 +77,30 @@ dependencies = [ [[package]] org = "ballerina" name = "graphql" -version = "1.8.1" +version = "1.11.2" dependencies = [ {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.regexp"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "oauth2"}, {org = "ballerina", name = "task"}, {org = "ballerina", name = "time"}, + {org = "ballerina", name = "uuid"}, {org = "ballerina", name = "websocket"} ] modules = [ {org = "ballerina", packageName = "graphql", moduleName = "graphql"}, + {org = "ballerina", packageName = "graphql", moduleName = "graphql.dataloader"}, {org = "ballerina", packageName = "graphql", moduleName = "graphql.parser"}, {org = "ballerina", packageName = "graphql", moduleName = "graphql.subgraph"} ] @@ -103,7 +108,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.8.7" +version = "2.10.15" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, @@ -128,13 +133,14 @@ dependencies = [ {org = "ballerina", name = "url"} ] modules = [ - {org = "ballerina", packageName = "http", moduleName = "http"} + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} ] [[package]] org = "ballerina" name = "io" -version = "1.4.1" +version = "1.6.1" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -151,7 +157,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.8.0" +version = "2.10.0" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -188,6 +194,15 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "lang.int" @@ -239,7 +254,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.7.1" +version = "2.9.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -253,7 +268,7 @@ modules = [ [[package]] org = "ballerina" name = "mime" -version = "2.7.1" +version = "2.9.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -263,7 +278,7 @@ dependencies = [ [[package]] org = "ballerina" name = "oauth2" -version = "2.8.0" +version = "2.10.0" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -276,7 +291,7 @@ dependencies = [ [[package]] org = "ballerina" name = "observe" -version = "1.0.7" +version = "1.2.3" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] @@ -284,7 +299,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.6.0" +version = "1.8.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} @@ -293,7 +308,7 @@ dependencies = [ [[package]] org = "ballerina" name = "task" -version = "2.3.2" +version = "2.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} @@ -305,7 +320,8 @@ name = "test" version = "0.0.0" scope = "testOnly" dependencies = [ - {org = "ballerina", name = "jballerina.java"} + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} ] modules = [ {org = "ballerina", packageName = "test", moduleName = "test"} @@ -314,7 +330,7 @@ modules = [ [[package]] org = "ballerina" name = "time" -version = "2.2.5" +version = "2.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] @@ -322,15 +338,26 @@ dependencies = [ [[package]] org = "ballerina" name = "url" -version = "2.2.4" +version = "2.4.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "uuid" +version = "1.7.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "time"} +] + [[package]] org = "ballerina" name = "websocket" -version = "2.8.2" +version = "2.10.2" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "constraint"}, diff --git a/campus/frontend/.fvmrc b/campus/frontend/.fvmrc new file mode 100644 index 00000000..2705bc24 --- /dev/null +++ b/campus/frontend/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.13.1" +} \ No newline at end of file diff --git a/campus/frontend/.gitignore b/campus/frontend/.gitignore new file mode 100644 index 00000000..9e366fe3 --- /dev/null +++ b/campus/frontend/.gitignore @@ -0,0 +1,3 @@ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/campus/frontend/.vscode/settings.json b/campus/frontend/.vscode/settings.json index 16a2796b..e36000ad 100644 --- a/campus/frontend/.vscode/settings.json +++ b/campus/frontend/.vscode/settings.json @@ -1,7 +1,8 @@ { - "githubPullRequests.ignoredPullRequestBranches": [ - "main" - ], - "editor.formatOnSave": true, - "editor.formatOnPaste": true + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ], + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "dart.flutterSdkPath": ".fvm/versions/3.13.1" } \ No newline at end of file diff --git a/campus/frontend/lib/avinya/enrollment/lib/screens/students_screen.dart b/campus/frontend/lib/avinya/enrollment/lib/screens/students_screen.dart index f0ae8ad6..dd40c7a9 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/screens/students_screen.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/screens/students_screen.dart @@ -11,6 +11,21 @@ class StudentsScreen extends StatefulWidget { class _StudentsScreenState extends State { @override Widget build(BuildContext context) { - return const Students(); + return Scaffold( + appBar: AppBar( + title: Text("Student Enrollment & Records", + style: TextStyle(color: Colors.black)), + backgroundColor: Color.fromARGB(255, 120, 224, 158), + ), + body: SingleChildScrollView( + child: Container( + child: Column( + children: [ + Students(), + ], + ), + ), + ), + ); } -} \ No newline at end of file +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart new file mode 100644 index 00000000..3897c340 --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart @@ -0,0 +1,178 @@ +import 'package:excel/excel.dart'; +import 'package:flutter/material.dart'; +import 'package:attendance/data/activity_attendance.dart'; +import 'package:gallery/data/person.dart'; +import 'package:intl/intl.dart'; + +class AttendanceSummaryExcelReportExport extends StatefulWidget { + final List fetchedDailyAttendanceSummaryData; + final List columnNames; + final Function() updateExcelState; + final bool isFetching; + final String formattedStartDate; + final String formattedEndDate; + + const AttendanceSummaryExcelReportExport( + {Key? key, + required this.fetchedDailyAttendanceSummaryData, + required this.columnNames, + required this.updateExcelState, + required this.isFetching, + required this.formattedStartDate, + required this.formattedEndDate + }) + : super(key: key); + + @override + _AttendanceSummaryExcelReportExportState createState() => _AttendanceSummaryExcelReportExportState(); +} + +class _AttendanceSummaryExcelReportExportState extends State { + List _fetchedDailyAttendanceSummaryData = []; + List columnNames = []; + List columnNamesWithoutDates = []; + + + void exportToExcel() async { + await widget.updateExcelState(); + + + setState(() { + this._fetchedDailyAttendanceSummaryData = widget.fetchedDailyAttendanceSummaryData; + this.columnNames = widget.columnNames; + }); + + if (_fetchedDailyAttendanceSummaryData.length > 0) { + + + if (columnNamesWithoutDates.isEmpty) { + columnNamesWithoutDates.addAll([ + "Date", + "Daily Count", + "Daily Attendance Percentage", + "Late Count", + "Late Attendance Percentage", + "Total Count" + ]); + } + + final excel = Excel.createExcel(); + final Sheet sheet = excel[excel.getDefaultSheet()!]; + + // Styling for organization header + final organizationHeaderStyle = CellStyle( + bold: true, + backgroundColorHex: '#807f7d', + horizontalAlign: HorizontalAlign.Center, + ); + + // Styling for row cells + final rowCellsStyle = CellStyle( + horizontalAlign: HorizontalAlign.Center, + ); + + // Adding organization header + sheet.merge( + CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0), + CellIndex.indexByColumnRow( + columnIndex: columnNamesWithoutDates.length - 1, rowIndex: 0), + ); + + // Adding subheaders + final subHeaderStyle = CellStyle( + bold: true, + horizontalAlign: HorizontalAlign.Center, + backgroundColorHex: '#a3a3a2', + textWrapping: TextWrapping.WrapText, + ); + + // Adding column headers + for (var colIndex = 0; + colIndex < columnNamesWithoutDates.length; + colIndex++) { + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) + .value = columnNamesWithoutDates[colIndex]; + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) + .cellStyle = subHeaderStyle; + } + sheet.setColWidth(0, 10); + sheet.setColWidth(1, 25); + sheet.setColWidth(2, 26); + sheet.setColWidth(3, 20); + sheet.setColWidth(4, 25); + sheet.setColWidth(5, 26); + + + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)).value = + "Avinya Foundation Daily Attendance Summary Report From ${widget.formattedStartDate} to ${widget.formattedEndDate}"; + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) + .cellStyle = organizationHeaderStyle; + + if (_fetchedDailyAttendanceSummaryData.isNotEmpty) { + + for (var index = 0; index < _fetchedDailyAttendanceSummaryData.length; index++) { + var dailyAttendanceSummaryData = _fetchedDailyAttendanceSummaryData[index]; + + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: index + 2)) + .value = dailyAttendanceSummaryData.sign_in_date?.toString() ?? ''; + + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: index + 2)) + .value = dailyAttendanceSummaryData.present_count?.toString() ?? ''; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 2, rowIndex: index + 2)) + .value = + (dailyAttendanceSummaryData.present_attendance_percentage?.toString() ?? '') + "%"; // update bank branch name + + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: index + 2)) + .value = dailyAttendanceSummaryData.late_count?.toString() ?? ''; + + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: index + 2)) + .value = (dailyAttendanceSummaryData.late_attendance_percentage?.toString()??'') + "%"; + + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: index + 2)) + .value = dailyAttendanceSummaryData.total_count?.toString()??''; + + } + } + + excel.save(fileName: "DailyAttendanceSummaryReport_${widget.formattedStartDate}_to_${widget.formattedEndDate}.xlsx"); + } + +} + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: widget.isFetching, + child: ElevatedButton.icon( + icon: Icon(Icons.download), + label: Text('Excel Export'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurpleAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10) + ), + ), + onPressed: (){ + exportToExcel(); + }, + + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index b8c2ff1a..38cbb1e9 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -1,15 +1,476 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:attendance/data/activity_attendance.dart'; +import 'package:attendance/data/organization.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:intl/intl.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:attendance/widgets/date_range_picker.dart'; + +import 'attendance_summary_excel_report_export.dart'; + +enum AvinyaTypeId { Empower, IT, CS } + +const avinyaTypeId = { + AvinyaTypeId.Empower: 37, + AvinyaTypeId.IT: 10, + AvinyaTypeId.CS: 96 +}; + class Students extends StatefulWidget { const Students({super.key}); @override - State createState() => _StudentsState(); + State createState() { + return _StudentsState(); + } } class _StudentsState extends State { + List _fetchedDailyAttendanceSummaryData = []; + List _fetchedExcelReportData = []; + late Future> _fetchBatchData; + bool _isFetching = false; + Organization? _selectedValue; + AvinyaTypeId _selectedAvinyaTypeId = AvinyaTypeId.Empower; + List filteredAvinyaTypeIdValues = [ + AvinyaTypeId.Empower, + AvinyaTypeId.IT, + AvinyaTypeId.CS + ]; + + List columnNames = []; + + late DataTableSource _data; + + List filteredStudents = []; + + //calendar specific variables + + @override + void initState() { + super.initState(); + _fetchBatchData = _loadBatchData(); + // filteredStudents = _fetchedDailyAttendanceSummaryData; + } + + Future> _loadBatchData() async { + return await fetchOrganizationsByAvinyaType(86); + } + + void updateExcelState() { + AttendanceSummaryExcelReportExport( + fetchedDailyAttendanceSummaryData: _fetchedDailyAttendanceSummaryData, + columnNames: columnNames, + updateExcelState: updateExcelState, + isFetching: _isFetching, + formattedStartDate: _selectedValue!.organization_metadata[0].value!, + formattedEndDate: _selectedValue!.organization_metadata[1].value!, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _data = MyData(_fetchedDailyAttendanceSummaryData, updateSelected); + } + + void updateSelected(int index, bool value, List selected) { + setState(() { + selected[index] = value; + }); + } + + List _buildDataColumns() { + List ColumnNames = []; + + ColumnNames.add(DataColumn( + label: Text('Date', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + ColumnNames.add(DataColumn( + label: Text('Daily Count', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + ColumnNames.add(DataColumn( + label: Text('Daily Attendance Percentage', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + ColumnNames.add(DataColumn( + label: Text('Late Count', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + ColumnNames.add(DataColumn( + label: Text('Late Attendance Percentage', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + ColumnNames.add(DataColumn( + label: Text('Total Count', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + return ColumnNames; + } + + // void searchStudents(String query) { + // setState(() { + // if (query.isEmpty) { + // filteredStudents = _fetchedDailyAttendanceSummaryData; + // } else { + // query = query.toLowerCase(); + + // filteredStudents = _fetchedDailyAttendanceSummaryData.where((student) { + // final presentCountString = + // student.present_count?.toString().toLowerCase() ?? ''; + // final attendancePercentageString = + // student.present_attendance_percentage?.toString().toLowerCase() ?? + // ''; + + // return presentCountString.contains(query) || + // attendancePercentageString.contains(query); + // }).toList(); + // } + // }); + // } + void searchStudents(String query) { + setState(() { + if (query.isEmpty) { + filteredStudents = _fetchedDailyAttendanceSummaryData; + } else { + filteredStudents = _fetchedDailyAttendanceSummaryData.where((student) { + print('Searching for: $query'); + print('Present count: ${student.present_count}'); + print( + 'Attendance percentage: ${student.present_attendance_percentage}'); + + final lowerCaseQuery = query.toLowerCase(); + final presentCountString = student.present_count?.toString() ?? ''; + final attendancePercentageString = + student.present_attendance_percentage?.toString() ?? ''; + + return presentCountString.contains(lowerCaseQuery) || + attendancePercentageString.contains(lowerCaseQuery); + }).toList(); + } + _data = MyData(filteredStudents, updateSelected); + }); + } + + @override + void dispose() { + super.dispose(); + } + @override Widget build(BuildContext context) { - return const Center(child: Text('Students enrollment'),); + return SingleChildScrollView( + child: Column( + children: [ + Wrap( + children: [ + Padding( + padding: EdgeInsets.only(top: 20, left: 20), + child: Row( + children: [ + Text('Select a Batch :'), + SizedBox( + width: 10, + ), + FutureBuilder>( + future: _fetchBatchData, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Container( + margin: EdgeInsets.only(top: 10), + child: SpinKitCircle( + color: (Colors.deepPurpleAccent), + size: 70, + ), + ); + } else if (snapshot.hasError) { + return const Center( + child: Text('Something went wrong...'), + ); + } else if (!snapshot.hasData) { + return const Center( + child: Text('No batch found'), + ); + } + final batchData = snapshot.data!; + return DropdownButton( + value: _selectedValue, + items: batchData.map((Organization batch) { + return DropdownMenuItem( + value: batch, + child: Text(batch.name!.name_en ?? '')); + }).toList(), + onChanged: (Organization? newValue) async { + if (newValue == null) { + return; + } + + if (newValue.organization_metadata.isEmpty) { + return; + } + + if (DateTime.parse(newValue + .organization_metadata[1].value + .toString()) + .isBefore(DateTime.parse('2024-03-01'))) { + filteredAvinyaTypeIdValues = [ + AvinyaTypeId.Empower + ]; + } else { + filteredAvinyaTypeIdValues = [ + AvinyaTypeId.Empower, + AvinyaTypeId.IT, + AvinyaTypeId.CS + ]; + } + + setState(() { + this._isFetching = true; + filteredAvinyaTypeIdValues; + }); + + if (filteredAvinyaTypeIdValues + .contains(_selectedAvinyaTypeId)) { + _fetchedDailyAttendanceSummaryData = + await getDailyAttendanceSummaryReport( + newValue.id!, + avinyaTypeId[_selectedAvinyaTypeId]!, + newValue.organization_metadata[0].value!, + newValue.organization_metadata[1].value!, + ); + } else { + _selectedAvinyaTypeId = + filteredAvinyaTypeIdValues.first; + + _fetchedDailyAttendanceSummaryData = + await getDailyAttendanceSummaryReport( + newValue.id!, + avinyaTypeId[_selectedAvinyaTypeId]!, + newValue.organization_metadata[0].value!, + newValue.organization_metadata[1].value!, + ); + } + + setState(() { + _selectedValue = newValue; + this._isFetching = false; + _selectedAvinyaTypeId; + _data = MyData( + _fetchedDailyAttendanceSummaryData, + updateSelected); + filteredStudents = + _fetchedDailyAttendanceSummaryData; + }); + }); + }, + ), + SizedBox( + width: 30, + ), + Text('Select a Programme :'), + SizedBox( + width: 10, + ), + DropdownButton( + value: _selectedAvinyaTypeId, + items: filteredAvinyaTypeIdValues + .map( + (typeId) => DropdownMenuItem( + value: typeId, + child: Text(typeId.name.toUpperCase()), + ), + ) + .toList(), + onChanged: (AvinyaTypeId? value) async { + if (value == null) { + return; + } + + if (_selectedValue == null || + _selectedValue!.organization_metadata.length == + 0) { + return; + } + + setState(() { + this._isFetching = true; + }); + + _fetchedDailyAttendanceSummaryData = + await getDailyAttendanceSummaryReport( + _selectedValue!.id!, + avinyaTypeId[value]!, + _selectedValue!.organization_metadata[0].value!, + _selectedValue!.organization_metadata[1].value!, + ); + + setState(() { + _selectedAvinyaTypeId = value; + this._isFetching = false; + _data = MyData(_fetchedDailyAttendanceSummaryData, + updateSelected); + filteredStudents = + _fetchedDailyAttendanceSummaryData; + }); + }), + SizedBox( + width: 30, + ), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: 300, + child: TextField( + decoration: InputDecoration( + labelText: 'Search by Name or NIC', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.search), + ), + onChanged: (query) { + searchStudents(query); + }, + ), + ), + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: Container( + alignment: Alignment.bottomRight, + margin: EdgeInsets.only(right: 20.0), + width: 25.0, + height: 30.0, + child: _selectedValue != null + ? AttendanceSummaryExcelReportExport( + fetchedDailyAttendanceSummaryData: + filteredStudents, + columnNames: columnNames, + updateExcelState: updateExcelState, + isFetching: _isFetching, + formattedStartDate: _selectedValue! + .organization_metadata[0].value!, + formattedEndDate: _selectedValue! + .organization_metadata[1].value!, + ) + : SizedBox(), + ), + ) + ], + ), + ), + SizedBox( + height: 10, + ), + Wrap(children: [ + if (_isFetching) + Container( + margin: EdgeInsets.only(top: 180), + child: SpinKitCircle( + color: (Color.fromARGB(255, 74, 161, + 70)), // Customize the color of the indicator + size: 50, // Customize the size of the indicator + ), + ) + else if (_fetchedDailyAttendanceSummaryData.length > 0) + ScrollConfiguration( + behavior: + ScrollConfiguration.of(context).copyWith(dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }), + child: PaginatedDataTable( + showCheckboxColumn: false, + source: _data, + columns: _buildDataColumns(), + // header: const Center(child: Text('Daily Attendance')), + columnSpacing: 100, + horizontalMargin: 60, + rowsPerPage: 20, + ), + ) + else + Container( + margin: EdgeInsets.all(20), + child: Text('No Students Records found'), + ), + ]), + ], + ), + ], + ), + ); + } +} + +class MyData extends DataTableSource { + MyData(this._fetchedDailyAttendanceSummaryData, this.updateSelected); + + final List _fetchedDailyAttendanceSummaryData; + final Function(int, bool, List) updateSelected; + + @override + DataRow? getRow(int index) { + if (index == 0) { + List cells = List.filled(6, DataCell.empty); + return DataRow( + cells: cells, + ); + } + + if (_fetchedDailyAttendanceSummaryData.length > 0 && + index <= _fetchedDailyAttendanceSummaryData.length) { + List cells = List.filled(6, DataCell.empty); + + cells[0] = DataCell(Center( + child: Text( + _fetchedDailyAttendanceSummaryData[index - 1].sign_in_date!))); + + cells[1] = DataCell(Center( + child: Text(_fetchedDailyAttendanceSummaryData[index - 1] + .present_count! + .toString()))); + + cells[2] = DataCell(Center( + child: Text(_fetchedDailyAttendanceSummaryData[index - 1] + .present_attendance_percentage! + .toString() + + " %"))); + + cells[3] = DataCell(Center( + child: Text(_fetchedDailyAttendanceSummaryData[index - 1] + .late_count! + .toString()))); + + cells[4] = DataCell(Center( + child: Text(_fetchedDailyAttendanceSummaryData[index - 1] + .late_attendance_percentage! + .toString() + + " %"))); + + cells[5] = DataCell(Center( + child: Text(_fetchedDailyAttendanceSummaryData[index - 1] + .total_count + .toString()))); + + return DataRow(cells: cells); + } + + return null; // Return null for invalid index values } -} \ No newline at end of file + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount { + int count = 0; + count = _fetchedDailyAttendanceSummaryData.length + 1; + return count; + } + + @override + int get selectedRowCount => 0; +} diff --git a/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index f39efc63..266dbd0e 100644 --- a/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,6 +1,6 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/Users/yujith/flutter -FLUTTER_APPLICATION_PATH=/Users/yujith/Avinya/avinya_apps/campus/frontend/lib/avinya/pcti_notes +FLUTTER_ROOT=/Users/yujith/fvm/versions/3.13.1 +FLUTTER_APPLICATION_PATH=/Users/yujith/Developer/Avinya/avinya_apps/campus/frontend/lib/avinya/pcti_notes COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build FLUTTER_BUILD_NAME=1.0.0 diff --git a/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/flutter_export_environment.sh b/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/flutter_export_environment.sh index 54e9812e..a61b6e7d 100755 --- a/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/campus/frontend/lib/avinya/pcti_notes/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,7 +1,7 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/yujith/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/yujith/Avinya/avinya_apps/campus/frontend/lib/avinya/pcti_notes" +export "FLUTTER_ROOT=/Users/yujith/fvm/versions/3.13.1" +export "FLUTTER_APPLICATION_PATH=/Users/yujith/Developer/Avinya/avinya_apps/campus/frontend/lib/avinya/pcti_notes" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=1.0.0" From bf66b1909de2427a9f55e714a17ddbd2a88b1dff Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Thu, 26 Sep 2024 13:42:03 +0530 Subject: [PATCH 2/6] student update WIP --- campus/frontend/assets/config/dev-cloud.json | 21 +- campus/frontend/assets/config/dev.json | 21 +- campus/frontend/assets/config/prod.json | 21 +- campus/frontend/assets/config/stag.json | 21 +- .../avinya/enrollment/lib/data/person.dart | 166 ++++-- .../lib/screens/student_update_screen.dart | 40 ++ ...ttendance_summary_excel_report_export.dart | 178 ------- .../lib/widgets/person_data_excel_report.dart | 174 +++++++ .../lib/widgets/student_update.dart | 473 ++++++++++++++++++ .../enrollment/lib/widgets/students.dart | 163 +++--- campus/frontend/lib/config/app_config.dart | 2 + 11 files changed, 941 insertions(+), 339 deletions(-) create mode 100644 campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart delete mode 100644 campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart create mode 100644 campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart create mode 100644 campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart diff --git a/campus/frontend/assets/config/dev-cloud.json b/campus/frontend/assets/config/dev-cloud.json index 9314daf8..dbb67e2e 100644 --- a/campus/frontend/assets/config/dev-cloud.json +++ b/campus/frontend/assets/config/dev-cloud.json @@ -1,12 +1,11 @@ { - "campusProfileBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/1.0.0", - "campusAttendanceBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/1.0.0", - "campusPctiNotesBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", - "campusPctiFeedbackBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", - "campusAssetsBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/1.0.0", - "choreo_sts_endpoint" : "https://sts.choreo.dev/oauth2/token", - "asgardeo_token_endpoint" : "https://api.asgardeo.io/t/avinyatest/oauth2/token", - "logout_url" : "https://api.asgardeo.io/t/avinyatest/oidc/logout" -} - - + "campusProfileBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/1.0.0", + "campusAttendanceBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/1.0.0", + "campusPctiNotesBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", + "campusPctiFeedbackBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", + "campusAssetsBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/1.0.0", + "campusEnrollmentsBffApiUrl": "http://localhost:9095", + "choreo_sts_endpoint": "https://sts.choreo.dev/oauth2/token", + "asgardeo_token_endpoint": "https://api.asgardeo.io/t/avinyatest/oauth2/token", + "logout_url": "https://api.asgardeo.io/t/avinyatest/oidc/logout" +} \ No newline at end of file diff --git a/campus/frontend/assets/config/dev.json b/campus/frontend/assets/config/dev.json index 77503b33..21e39740 100644 --- a/campus/frontend/assets/config/dev.json +++ b/campus/frontend/assets/config/dev.json @@ -1,12 +1,11 @@ { - "campusProfileBffApiUrl" : "http://localhost:9090", - "campusAttendanceBffApiUrl" : "http://localhost:9091", - "campusPctiNotesBffApiUrl" : "http://localhost:9092", - "campusPctiFeedbackBffApiUrl" : "http://localhost:9093", - "campusAssetsBffApiUrl" : "http://localhost:9094", - "choreo_sts_endpoint" : "https://sts.choreo.dev/oauth2/token", - "asgardeo_token_endpoint" : "https://api.asgardeo.io/t/avinyatest/oauth2/token", - "logout_url" : "https://api.asgardeo.io/t/avinyatest/oidc/logout" -} - - + "campusProfileBffApiUrl": "http://localhost:9090", + "campusAttendanceBffApiUrl": "http://localhost:9091", + "campusPctiNotesBffApiUrl": "http://localhost:9092", + "campusPctiFeedbackBffApiUrl": "http://localhost:9093", + "campusAssetsBffApiUrl": "http://localhost:9094", + "campusEnrollmentsBffApiUrl": "http://localhost:9095", + "choreo_sts_endpoint": "https://sts.choreo.dev/oauth2/token", + "asgardeo_token_endpoint": "https://api.asgardeo.io/t/avinyatest/oauth2/token", + "logout_url": "https://api.asgardeo.io/t/avinyatest/oidc/logout" +} \ No newline at end of file diff --git a/campus/frontend/assets/config/prod.json b/campus/frontend/assets/config/prod.json index 21a58d88..f952d63e 100644 --- a/campus/frontend/assets/config/prod.json +++ b/campus/frontend/assets/config/prod.json @@ -1,12 +1,11 @@ { - "campusProfileBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/0.9.0", - "campusAttendanceBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/0.9.0", - "campusPctiNotesBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", - "campusPctiFeedbackBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", - "campusAssetsBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/0.9.0", - "choreo_sts_endpoint" : "https://sts.choreo.dev/oauth2/token", - "asgardeo_token_endpoint" : "https://api.asgardeo.io/t/avinyaacademy/oauth2/token", - "logout_url" : "https://api.asgardeo.io/t/avinyaacademy/oidc/logout" -} - - + "campusProfileBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/0.9.0", + "campusAttendanceBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/0.9.0", + "campusPctiNotesBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", + "campusPctiFeedbackBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", + "campusAssetsBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-prod.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/0.9.0", + "campusEnrollmentsBffApiUrl": "http://localhost:9095", + "choreo_sts_endpoint": "https://sts.choreo.dev/oauth2/token", + "asgardeo_token_endpoint": "https://api.asgardeo.io/t/avinyaacademy/oauth2/token", + "logout_url": "https://api.asgardeo.io/t/avinyaacademy/oidc/logout" +} \ No newline at end of file diff --git a/campus/frontend/assets/config/stag.json b/campus/frontend/assets/config/stag.json index 63d67fea..68ed0ee8 100644 --- a/campus/frontend/assets/config/stag.json +++ b/campus/frontend/assets/config/stag.json @@ -1,12 +1,11 @@ { - "campusProfileBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/0.9.0", - "campusAttendanceBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/0.9.0", - "campusPctiNotesBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", - "campusPctiFeedbackBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", - "campusAssetsBffApiUrl" : "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/0.9.0", - "choreo_sts_endpoint" : "https://sts.choreo.dev/oauth2/token", - "asgardeo_token_endpoint" : "https://api.asgardeo.io/t/avinyaacademy/oauth2/token", - "logout_url" : "https://api.asgardeo.io/t/avinyaacademy/oidc/logout" -} - - + "campusProfileBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/profile-bff/0.9.0", + "campusAttendanceBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/attendance-bff/0.9.0", + "campusPctiNotesBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-notes-bff/1.0.0", + "campusPctiFeedbackBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/pcti-feedback-bff/1.0.0", + "campusAssetsBffApiUrl": "https://3a907137-52a3-4196-9e0d-22d054ea5789-dev.e1-us-east-azure.choreoapis.dev/fieg/asset-bff/0.9.0", + "campusEnrollmentsBffApiUrl": "http://localhost:9095", + "choreo_sts_endpoint": "https://sts.choreo.dev/oauth2/token", + "asgardeo_token_endpoint": "https://api.asgardeo.io/t/avinyaacademy/oauth2/token", + "logout_url": "https://api.asgardeo.io/t/avinyaacademy/oidc/logout" +} \ No newline at end of file diff --git a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart index 2e3276aa..085d3ea3 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart @@ -2,10 +2,86 @@ import 'dart:developer'; //import 'package:ShoolManagementSystem/src/data/address.dart'; import 'package:gallery/avinya/asset/lib/data/address.dart'; +import 'package:gallery/avinya/attendance/lib/data.dart'; import 'package:gallery/config/app_config.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; +class MainOrganization { + int? id; + String? description; + String? notes; + String? address; + AvinyaType? avinya_type; + Name? name; + + MainOrganization({ + this.id, + this.description, + this.notes, + this.address, + this.avinya_type, + this.name, + }); + + factory MainOrganization.fromJson(Map json) { + return MainOrganization( + id: json['id'], + description: json['description'], + notes: json['notes'], + address: json['address'], + avinya_type: json['avinya_type'] != null + ? AvinyaType.fromJson(json['avinya_type']) + : null, + name: json['name'] != null ? Name.fromJson(json['name']) : null, + ); + } + + Map toJson() => { + if (id != null) 'id': id, + if (description != null) 'description': description, + if (notes != null) 'notes': notes, + if (address != null) 'address': address, + if (avinya_type != null) 'avinya_type': avinya_type!.toJson(), + if (name != null) 'name': name!.toJson(), + }; +} + +class AvinyaType { + int? id; + String? name; + + AvinyaType({this.id, this.name}); + + factory AvinyaType.fromJson(Map json) { + return AvinyaType( + id: json['id'], + name: json['name'], + ); + } + + Map toJson() => { + if (id != null) 'id': id, + if (name != null) 'name': name, + }; +} + +class Name { + String? nameEn; + + Name({this.nameEn}); + + factory Name.fromJson(Map json) { + return Name( + nameEn: json['name_en'], + ); + } + + Map toJson() => { + if (nameEn != null) 'name_en': nameEn, + }; +} + class Person { int? id; String? record_type; @@ -29,31 +105,32 @@ class Person { String? email; Address? permanent_address; Address? mailing_address; + MainOrganization? organization; - Person({ - this.id, - this.record_type, - this.preferred_name, - this.full_name, - this.notes, - this.date_of_birth, - this.sex, - this.avinya_type_id, - this.passport_no, - this.permanent_address_id, - this.digital_id, - this.mailing_address_id, - this.nic_no, - this.id_no, - this.phone, - this.organization_id, - this.asgardeo_id, - this.jwt_sub_id, - this.jwt_email, - this.email, - this.permanent_address, - this.mailing_address, - }); + Person( + {this.id, + this.record_type, + this.preferred_name, + this.full_name, + this.notes, + this.date_of_birth, + this.sex, + this.avinya_type_id, + this.passport_no, + this.permanent_address_id, + this.digital_id, + this.mailing_address_id, + this.nic_no, + this.id_no, + this.phone, + this.organization_id, + this.asgardeo_id, + this.jwt_sub_id, + this.jwt_email, + this.email, + this.permanent_address, + this.mailing_address, + this.organization}); factory Person.fromJson(Map json) { return Person( @@ -81,6 +158,9 @@ class Person { json['permanent_address'] != null ? json['permanent_address'] : {}), mailing_address: Address.fromJson( json['mailing_address'] != null ? json['mailing_address'] : {}), + organization: json['organization'] != null + ? MainOrganization.fromJson(json['organization']) + : null, ); } @@ -111,26 +191,48 @@ class Person { 'permanent_address': permanent_address!.toJson(), if (mailing_address != null) 'mailing_address': mailing_address!.toJson(), + if (organization != null) 'organization': organization!.toJson(), }; } -Future> fetchPersons() async { +// Future> fetchPersons() async { +// final response = await http.get( +// Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), +// headers: { +// 'Content-Type': 'application/json; charset=UTF-8', +// 'accept': 'application/json', +// 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, +// }, +// ); + +// if (response.statusCode == 200) { +// var resultsJson = json.decode(response.body).cast>(); +// List persons = +// await resultsJson.map((json) => Person.fromJson(json)).toList(); +// return persons; +// } else { +// throw Exception('Failed to load Person'); +// } +// } + +Future> fetchPersons( + int organization_id, int avinya_type_id) async { final response = await http.get( - Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), + Uri.parse( + '${AppConfig.campusEnrollmentsBffApiUrl}/persons/$organization_id/$avinya_type_id'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'accept': 'application/json', - 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, + 'Authorization': 'Bearer ${AppConfig.campusBffApiKey}', }, ); - - if (response.statusCode == 200) { + if (response.statusCode > 199 && response.statusCode < 300) { var resultsJson = json.decode(response.body).cast>(); - List persons = + List activityAttendances = await resultsJson.map((json) => Person.fromJson(json)).toList(); - return persons; + return activityAttendances; } else { - throw Exception('Failed to load Person'); + throw Exception('Failed to get Daily Attendances Summary Data'); } } diff --git a/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart b/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart new file mode 100644 index 00000000..9edf965e --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/avinya/enrollment/lib/widgets/student_update.dart'; + +class StudentUpdateScreen extends StatefulWidget { + final int? id; + + const StudentUpdateScreen({Key? key, this.id}) : super(key: key); + + @override + State createState() => _StudentUpdateScreenState(); +} + +class _StudentUpdateScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Student Enrollment & Records", + style: TextStyle(color: Colors.black)), + backgroundColor: Color.fromARGB(255, 120, 224, 158), + ), + body: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + MediaQuery.of(context).size.height, // Ensure minimum height + ), + child: IntrinsicHeight( + child: Column( + children: [ + StudentUpdate(id: widget.id), // Pass the ID + // Add other widgets or buttons if needed + ], + ), + ), + ), + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart deleted file mode 100644 index 3897c340..00000000 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/attendance_summary_excel_report_export.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:excel/excel.dart'; -import 'package:flutter/material.dart'; -import 'package:attendance/data/activity_attendance.dart'; -import 'package:gallery/data/person.dart'; -import 'package:intl/intl.dart'; - -class AttendanceSummaryExcelReportExport extends StatefulWidget { - final List fetchedDailyAttendanceSummaryData; - final List columnNames; - final Function() updateExcelState; - final bool isFetching; - final String formattedStartDate; - final String formattedEndDate; - - const AttendanceSummaryExcelReportExport( - {Key? key, - required this.fetchedDailyAttendanceSummaryData, - required this.columnNames, - required this.updateExcelState, - required this.isFetching, - required this.formattedStartDate, - required this.formattedEndDate - }) - : super(key: key); - - @override - _AttendanceSummaryExcelReportExportState createState() => _AttendanceSummaryExcelReportExportState(); -} - -class _AttendanceSummaryExcelReportExportState extends State { - List _fetchedDailyAttendanceSummaryData = []; - List columnNames = []; - List columnNamesWithoutDates = []; - - - void exportToExcel() async { - await widget.updateExcelState(); - - - setState(() { - this._fetchedDailyAttendanceSummaryData = widget.fetchedDailyAttendanceSummaryData; - this.columnNames = widget.columnNames; - }); - - if (_fetchedDailyAttendanceSummaryData.length > 0) { - - - if (columnNamesWithoutDates.isEmpty) { - columnNamesWithoutDates.addAll([ - "Date", - "Daily Count", - "Daily Attendance Percentage", - "Late Count", - "Late Attendance Percentage", - "Total Count" - ]); - } - - final excel = Excel.createExcel(); - final Sheet sheet = excel[excel.getDefaultSheet()!]; - - // Styling for organization header - final organizationHeaderStyle = CellStyle( - bold: true, - backgroundColorHex: '#807f7d', - horizontalAlign: HorizontalAlign.Center, - ); - - // Styling for row cells - final rowCellsStyle = CellStyle( - horizontalAlign: HorizontalAlign.Center, - ); - - // Adding organization header - sheet.merge( - CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0), - CellIndex.indexByColumnRow( - columnIndex: columnNamesWithoutDates.length - 1, rowIndex: 0), - ); - - // Adding subheaders - final subHeaderStyle = CellStyle( - bold: true, - horizontalAlign: HorizontalAlign.Center, - backgroundColorHex: '#a3a3a2', - textWrapping: TextWrapping.WrapText, - ); - - // Adding column headers - for (var colIndex = 0; - colIndex < columnNamesWithoutDates.length; - colIndex++) { - sheet - .cell(CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) - .value = columnNamesWithoutDates[colIndex]; - sheet - .cell(CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) - .cellStyle = subHeaderStyle; - } - sheet.setColWidth(0, 10); - sheet.setColWidth(1, 25); - sheet.setColWidth(2, 26); - sheet.setColWidth(3, 20); - sheet.setColWidth(4, 25); - sheet.setColWidth(5, 26); - - - sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)).value = - "Avinya Foundation Daily Attendance Summary Report From ${widget.formattedStartDate} to ${widget.formattedEndDate}"; - sheet - .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) - .cellStyle = organizationHeaderStyle; - - if (_fetchedDailyAttendanceSummaryData.isNotEmpty) { - - for (var index = 0; index < _fetchedDailyAttendanceSummaryData.length; index++) { - var dailyAttendanceSummaryData = _fetchedDailyAttendanceSummaryData[index]; - - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: index + 2)) - .value = dailyAttendanceSummaryData.sign_in_date?.toString() ?? ''; - - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: index + 2)) - .value = dailyAttendanceSummaryData.present_count?.toString() ?? ''; - - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 2, rowIndex: index + 2)) - .value = - (dailyAttendanceSummaryData.present_attendance_percentage?.toString() ?? '') + "%"; // update bank branch name - - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: index + 2)) - .value = dailyAttendanceSummaryData.late_count?.toString() ?? ''; - - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: index + 2)) - .value = (dailyAttendanceSummaryData.late_attendance_percentage?.toString()??'') + "%"; - - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: index + 2)) - .value = dailyAttendanceSummaryData.total_count?.toString()??''; - - } - } - - excel.save(fileName: "DailyAttendanceSummaryReport_${widget.formattedStartDate}_to_${widget.formattedEndDate}.xlsx"); - } - -} - - @override - Widget build(BuildContext context) { - return IgnorePointer( - ignoring: widget.isFetching, - child: ElevatedButton.icon( - icon: Icon(Icons.download), - label: Text('Excel Export'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.deepPurpleAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10) - ), - ), - onPressed: (){ - exportToExcel(); - }, - - ), - ); - } -} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart new file mode 100644 index 00000000..48d2d494 --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart @@ -0,0 +1,174 @@ +import 'package:excel/excel.dart'; +import 'package:flutter/material.dart'; +import 'package:attendance/data/activity_attendance.dart'; +import 'package:gallery/avinya/enrollment/lib/data/person.dart'; +import 'package:intl/intl.dart'; + +class PersonDataExcelReport extends StatefulWidget { + final List fetchedPersonData; + final List columnNames; + final Function() updateExcelState; + final bool isFetching; + final String formattedStartDate; + final String formattedEndDate; + + const PersonDataExcelReport( + {Key? key, + required this.fetchedPersonData, + required this.columnNames, + required this.updateExcelState, + required this.isFetching, + required this.formattedStartDate, + required this.formattedEndDate}) + : super(key: key); + + @override + _PersonDataExcelReportState createState() => _PersonDataExcelReportState(); +} + +class _PersonDataExcelReportState extends State { + List _fetchedPersonData = []; + List columnNames = []; + List columnNamesWithoutDates = []; + + void exportToExcel() async { + await widget.updateExcelState(); + + setState(() { + this._fetchedPersonData = widget.fetchedPersonData; + this.columnNames = widget.columnNames; + }); + + if (_fetchedPersonData.length > 0) { + if (columnNamesWithoutDates.isEmpty) { + columnNamesWithoutDates.addAll([ + "Date", + "Daily Count", + "Daily Attendance Percentage", + "Late Count", + "Late Attendance Percentage", + "Total Count" + ]); + } + + final excel = Excel.createExcel(); + final Sheet sheet = excel[excel.getDefaultSheet()!]; + + // Styling for organization header + final organizationHeaderStyle = CellStyle( + bold: true, + backgroundColorHex: '#807f7d', + horizontalAlign: HorizontalAlign.Center, + ); + + // Styling for row cells + final rowCellsStyle = CellStyle( + horizontalAlign: HorizontalAlign.Center, + ); + + // Adding organization header + sheet.merge( + CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0), + CellIndex.indexByColumnRow( + columnIndex: columnNamesWithoutDates.length - 1, rowIndex: 0), + ); + + // Adding subheaders + final subHeaderStyle = CellStyle( + bold: true, + horizontalAlign: HorizontalAlign.Center, + backgroundColorHex: '#a3a3a2', + textWrapping: TextWrapping.WrapText, + ); + + // Adding column headers + for (var colIndex = 0; + colIndex < columnNamesWithoutDates.length; + colIndex++) { + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) + .value = columnNamesWithoutDates[colIndex]; + sheet + .cell( + CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: 1)) + .cellStyle = subHeaderStyle; + } + sheet.setColWidth(0, 10); + sheet.setColWidth(1, 25); + sheet.setColWidth(2, 26); + sheet.setColWidth(3, 20); + sheet.setColWidth(4, 25); + sheet.setColWidth(5, 26); + + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) + .value = + "Avinya Foundation Daily Attendance Summary Report From ${widget.formattedStartDate} to ${widget.formattedEndDate}"; + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) + .cellStyle = organizationHeaderStyle; + + if (_fetchedPersonData.isNotEmpty) { + for (var index = 0; index < _fetchedPersonData.length; index++) { + var personData = _fetchedPersonData[index]; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 0, rowIndex: index + 2)) + .value = personData.preferred_name?.toString() ?? ''; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 1, rowIndex: index + 2)) + .value = personData.nic_no?.toString() ?? ''; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 2, rowIndex: index + 2)) + .value = + (personData.phone?.toString() ?? '') + + " "; // update bank branch name + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 3, rowIndex: index + 2)) + .value = personData.digital_id?.toString() ?? ''; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 4, rowIndex: index + 2)) + .value = (personData.date_of_birth?.toString() ?? '') + " "; + + sheet + .cell(CellIndex.indexByColumnRow( + columnIndex: 5, rowIndex: index + 2)) + .value = personData.organization_id?.toString() ?? ''; + } + } + + excel.save( + fileName: + "DailyAttendanceSummaryReport_${widget.formattedStartDate}_to_${widget.formattedEndDate}.xlsx"); + } + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: widget.isFetching, + child: ElevatedButton.icon( + icon: Icon(Icons.download), + label: Text('Excel Export'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurpleAccent, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + onPressed: () { + exportToExcel(); + }, + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart new file mode 100644 index 00000000..61a6deb6 --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -0,0 +1,473 @@ +// import 'package:flutter/material.dart'; + +// class StudentUpdate extends StatefulWidget { +// final int? id; +// const StudentUpdate({Key? key, this.id}) : super(key: key); + +// @override +// State createState() => _StudentUpdateState(); +// } + +// class _StudentUpdateState extends State { +// @override +// Widget build(BuildContext context) { +// return Column( +// children: [ +// Text('Details for student with ID: ${widget.id}'), +// // Add more widgets related to student details here +// ], +// ); +// } +// } + +import 'package:gallery/constants.dart'; +import 'package:flutter/material.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:gallery/data/person.dart'; +import 'package:sizer/sizer.dart'; +import 'package:intl/intl.dart'; + +class StudentUpdate extends StatefulWidget { + final int? id; + const StudentUpdate({Key? key, this.id}) : super(key: key); + + @override + State createState() => _StudentUpdateState(); +} + +class _StudentUpdateState extends State { + late Person userPerson = Person() + ..full_name = 'John' + ..nic_no = '12'; + + @override + void initState() { + super.initState(); + getUserPerson(); + } + + void getUserPerson() { + // Retrieve user data from local instance + Person user = campusAppsPortalInstance.getUserPerson(); + setState(() { + userPerson = user; + }); + } + + int calculateAge(String dateOfBirth) { + DateTime today = DateTime.now(); + DateTime dob = DateTime.parse(dateOfBirth); + int age = today.year - dob.year; + if (today.month < dob.month || + (today.month == dob.month && today.day < dob.day)) { + age--; + } + return age; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Container( + color: kOtherColor, + child: Column( + children: [ + sizedBox, + Container( + width: 100.w, + height: SizerUtil.deviceType == DeviceType.tablet ? 19.h : 15.h, + decoration: BoxDecoration( + color: kPrimaryColor, + borderRadius: kBottomBorderRadius, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + radius: SizerUtil.deviceType == DeviceType.tablet + ? 12.w + : 13.w, + backgroundColor: kSecondaryColor, + backgroundImage: + AssetImage('assets/images/student_profile.jpeg'), + ), + kWidthSizedBox, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${userPerson.full_name == null ? 'N/A' : userPerson.full_name!}', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == + DeviceType.mobile + ? 16.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 15.sp + : SizerUtil.deviceType == DeviceType.web + ? 7.sp + : 12.sp, + ), + ), + Text( + '${userPerson.organization == null ? 'N/A' : userPerson.organization!.name == null ? 'N/A' : userPerson.organization!.name!.name_en == null ? 'N/A' : userPerson.organization!.name!.name_en!}', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == + DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + ], + ) + ], + ), + ), + sizedBox, + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ProfileDetailRow( + title: 'Registration Number', + value: + '${userPerson.id == null ? 'N/A' : userPerson.id}'), + ProfileDetailRow( + title: 'Academic Year', + value: + '${userPerson.updated == null ? 'N/A' : '${DateFormat('yyyy').format(DateTime.parse(userPerson.updated!))} - ${DateFormat('yyyy').format(DateTime.parse(userPerson.updated!).add(Duration(days: 365)))} '}'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ProfileDetailRow( + title: 'Programme', + value: + '${userPerson.avinya_type == null ? 'N/A' : userPerson.avinya_type!.focus == null ? 'N/A' : userPerson.avinya_type!.focus}', + ), + ProfileDetailRow(title: 'Class', value: 'TBD'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ProfileDetailRow( + title: 'Date of Admission', + value: + '${userPerson.created == null ? 'N/A' : DateFormat('d MMM, yyyy').format(DateTime.parse(userPerson.created!))}'), + // ProfileDetailRow( + // title: 'Age', + // value: + // '${calculateAge(userPerson.date_of_birth!)} years old'), + ], + ), + sizedBox, + sizedBox, + sizedBox, + Text( + 'Student Information', + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontWeight: FontWeight.bold, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + sizedBox, + ProfileDetailColumn( + title: 'Full Name', + value: + '${userPerson.full_name == null ? 'N/A' : userPerson.full_name}', + ), + ProfileDetailColumn( + title: 'Preferred Name', + value: + '${userPerson.preferred_name == null ? 'N/A' : userPerson.preferred_name}', + ), + ProfileDetailColumn( + title: 'Date of Birth', + value: + '${userPerson.date_of_birth == null ? 'N/A' : DateFormat('d MMM, yyyy').format(DateTime.parse(userPerson.date_of_birth!))}', + ), + ProfileDetailColumn( + title: 'Gender', + value: '${userPerson.sex == null ? 'N/A' : userPerson.sex}', + ), + ProfileDetailColumn( + title: 'NIC Number', + value: + '${userPerson.nic_no == null ? 'N/A' : userPerson.nic_no}', + ), + ProfileDetailColumn( + title: 'Passport Number', + value: + '${userPerson.passport_no == null ? 'N/A' : userPerson.passport_no}', + ), + sizedBox, + sizedBox, + sizedBox, + Text( + 'Student Contact Information', + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontWeight: FontWeight.bold, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + sizedBox, + ProfileDetailColumn( + title: 'Personal Phone Number', + value: '${userPerson.phone == null ? 'N/A' : userPerson.phone}', + ), + ProfileDetailColumn( + title: 'Avinya Phone Number', + value: + '${userPerson.avinya_phone == null ? 'N/A' : userPerson.avinya_phone}', + ), + ProfileDetailColumn( + title: 'Email', + value: '${userPerson.email == null ? 'N/A' : userPerson.email}', + ), + ProfileDetailColumn( + title: 'Home Address', + value: + '${userPerson.permanent_address == null ? 'N/A' : userPerson.permanent_address!.street_address == null ? 'N/A' : userPerson.permanent_address!.street_address}', + ), + ProfileDetailColumn( + title: 'Mailing Address', + value: + '${userPerson.mailing_address == null ? 'N/A' : userPerson.mailing_address!.street_address == null ? 'N/A' : userPerson.mailing_address!.street_address}', + ), + sizedBox, + sizedBox, + sizedBox, + Text( + 'Student Bank Information', + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontWeight: FontWeight.bold, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + sizedBox, + ProfileDetailColumn( + title: 'Bank Name', + value: + '${userPerson.bank_name == null ? 'N/A' : userPerson.bank_name}', + ), + ProfileDetailColumn( + title: 'Bank Account Name', + value: + '${userPerson.bank_account_name == null ? 'N/A' : userPerson.bank_account_name}', + ), + ProfileDetailColumn( + title: 'Bank Account Number', + value: + '${userPerson.bank_account_number == null ? 'N/A' : userPerson.bank_account_number}', + ), + sizedBox, + sizedBox, + sizedBox, + Text( + 'Parent/Guardian Information', + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontWeight: FontWeight.bold, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + sizedBox, + ProfileDetailColumn( + title: 'Father Name', + value: + '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[0].preferred_name != null ? userPerson.parent_students[0].preferred_name : 'N/A'}', + ), + ProfileDetailColumn( + title: 'Mother Name', + value: + '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[1].preferred_name != null ? userPerson.parent_students[1].preferred_name : 'N/A'}', + ), + ProfileDetailColumn( + title: 'Father Phone Number', + value: + '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[0].phone != null ? userPerson.parent_students[0].phone : 'N/A'}', + ), + ProfileDetailColumn( + title: 'Mother Phone Number', + value: + '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[1].phone != null ? userPerson.parent_students[1].phone : 'N/A'}', + ), + sizedBox, + sizedBox, + sizedBox, + ], + ), + ), + ), + ); + } +} + +class ProfileDetailRow extends StatelessWidget { + const ProfileDetailRow({Key? key, required this.title, required this.value}) + : super(key: key); + final String title; + final String value; + @override + Widget build(BuildContext context) { + return Container( + width: 40.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + kHalfSizedBox, + Text( + value, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + kHalfSizedBox, + SizedBox( + width: 35.w, + child: Divider( + thickness: 1.0, + color: kTextBlackColor, + ), + ), + ], + ), + Icon( + Icons.lock_outline, + size: 6.sp, + color: kTextBlackColor, + ), + ], + ), + ); + } +} + +class ProfileDetailColumn extends StatelessWidget { + const ProfileDetailColumn( + {Key? key, required this.title, required this.value}) + : super(key: key); + final String title; + final String value; + @override + Widget build(BuildContext context) { + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + kHalfSizedBox, + Text( + value, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: kTextBlackColor, + fontSize: SizerUtil.deviceType == DeviceType.mobile + ? 15.sp + : SizerUtil.deviceType == DeviceType.tablet + ? 14.sp + : SizerUtil.deviceType == DeviceType.web + ? 6.sp + : 11.sp, + ), + ), + kHalfSizedBox, + SizedBox( + width: 92.w, + child: Divider( + thickness: 1.0, + color: kTextBlackColor, + ), + ) + ], + ), + Icon( + Icons.lock_outline, + size: 6.sp, + color: kTextBlackColor, + ), + ], + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index 38cbb1e9..b5c2b40f 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -4,11 +4,13 @@ import 'package:flutter/material.dart'; import 'package:attendance/data/activity_attendance.dart'; import 'package:attendance/data/organization.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:gallery/avinya/enrollment/lib/data/person.dart'; +import 'package:gallery/avinya/enrollment/lib/screens/student_update_screen.dart'; import 'package:intl/intl.dart'; import 'package:gallery/data/campus_apps_portal.dart'; import 'package:attendance/widgets/date_range_picker.dart'; -import 'attendance_summary_excel_report_export.dart'; +import 'person_data_excel_report.dart'; enum AvinyaTypeId { Empower, IT, CS } @@ -28,8 +30,8 @@ class Students extends StatefulWidget { } class _StudentsState extends State { - List _fetchedDailyAttendanceSummaryData = []; - List _fetchedExcelReportData = []; + List _fetchedPersonData = []; + List _fetchedExcelReportData = []; late Future> _fetchBatchData; bool _isFetching = false; Organization? _selectedValue; @@ -44,7 +46,7 @@ class _StudentsState extends State { late DataTableSource _data; - List filteredStudents = []; + List filteredStudents = []; //calendar specific variables @@ -52,7 +54,7 @@ class _StudentsState extends State { void initState() { super.initState(); _fetchBatchData = _loadBatchData(); - // filteredStudents = _fetchedDailyAttendanceSummaryData; + // filteredStudents = _fetchedPersonData; } Future> _loadBatchData() async { @@ -60,8 +62,8 @@ class _StudentsState extends State { } void updateExcelState() { - AttendanceSummaryExcelReportExport( - fetchedDailyAttendanceSummaryData: _fetchedDailyAttendanceSummaryData, + PersonDataExcelReport( + fetchedPersonData: _fetchedPersonData, columnNames: columnNames, updateExcelState: updateExcelState, isFetching: _isFetching, @@ -73,7 +75,7 @@ class _StudentsState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _data = MyData(_fetchedDailyAttendanceSummaryData, updateSelected); + _data = MyData(_fetchedPersonData, updateSelected, context); } void updateSelected(int index, bool value, List selected) { @@ -86,22 +88,22 @@ class _StudentsState extends State { List ColumnNames = []; ColumnNames.add(DataColumn( - label: Text('Date', + label: Text('Name', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); ColumnNames.add(DataColumn( - label: Text('Daily Count', + label: Text('NIC Number', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); ColumnNames.add(DataColumn( - label: Text('Daily Attendance Percentage', + label: Text('Birth Date', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); ColumnNames.add(DataColumn( - label: Text('Late Count', + label: Text('Digital ID', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); ColumnNames.add(DataColumn( - label: Text('Late Attendance Percentage', + label: Text('Gender', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); ColumnNames.add(DataColumn( - label: Text('Total Count', + label: Text('Organization', style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); return ColumnNames; @@ -110,11 +112,11 @@ class _StudentsState extends State { // void searchStudents(String query) { // setState(() { // if (query.isEmpty) { - // filteredStudents = _fetchedDailyAttendanceSummaryData; + // filteredStudents = _fetchedPersonData; // } else { // query = query.toLowerCase(); - // filteredStudents = _fetchedDailyAttendanceSummaryData.where((student) { + // filteredStudents = _fetchedPersonData.where((student) { // final presentCountString = // student.present_count?.toString().toLowerCase() ?? ''; // final attendancePercentageString = @@ -130,24 +132,22 @@ class _StudentsState extends State { void searchStudents(String query) { setState(() { if (query.isEmpty) { - filteredStudents = _fetchedDailyAttendanceSummaryData; + filteredStudents = _fetchedPersonData; } else { - filteredStudents = _fetchedDailyAttendanceSummaryData.where((student) { + filteredStudents = _fetchedPersonData.where((student) { print('Searching for: $query'); - print('Present count: ${student.present_count}'); - print( - 'Attendance percentage: ${student.present_attendance_percentage}'); + print('Present count: ${student.preferred_name}'); + print('Attendance percentage: ${student.nic_no}'); final lowerCaseQuery = query.toLowerCase(); - final presentCountString = student.present_count?.toString() ?? ''; - final attendancePercentageString = - student.present_attendance_percentage?.toString() ?? ''; + final presentCountString = student.preferred_name?.toString() ?? ''; + final attendancePercentageString = student.nic_no?.toString() ?? ''; return presentCountString.contains(lowerCaseQuery) || attendancePercentageString.contains(lowerCaseQuery); }).toList(); } - _data = MyData(filteredStudents, updateSelected); + _data = MyData(filteredStudents, updateSelected, context); }); } @@ -231,35 +231,25 @@ class _StudentsState extends State { if (filteredAvinyaTypeIdValues .contains(_selectedAvinyaTypeId)) { - _fetchedDailyAttendanceSummaryData = - await getDailyAttendanceSummaryReport( - newValue.id!, - avinyaTypeId[_selectedAvinyaTypeId]!, - newValue.organization_metadata[0].value!, - newValue.organization_metadata[1].value!, - ); + _fetchedPersonData = await fetchPersons( + newValue.id!, + avinyaTypeId[_selectedAvinyaTypeId]!); } else { _selectedAvinyaTypeId = filteredAvinyaTypeIdValues.first; - _fetchedDailyAttendanceSummaryData = - await getDailyAttendanceSummaryReport( - newValue.id!, - avinyaTypeId[_selectedAvinyaTypeId]!, - newValue.organization_metadata[0].value!, - newValue.organization_metadata[1].value!, - ); + _fetchedPersonData = await fetchPersons( + newValue.id!, + avinyaTypeId[_selectedAvinyaTypeId]!); } setState(() { _selectedValue = newValue; this._isFetching = false; _selectedAvinyaTypeId; - _data = MyData( - _fetchedDailyAttendanceSummaryData, - updateSelected); - filteredStudents = - _fetchedDailyAttendanceSummaryData; + _data = MyData(_fetchedPersonData, + updateSelected, context); + filteredStudents = _fetchedPersonData; }); }); }, @@ -296,21 +286,15 @@ class _StudentsState extends State { this._isFetching = true; }); - _fetchedDailyAttendanceSummaryData = - await getDailyAttendanceSummaryReport( - _selectedValue!.id!, - avinyaTypeId[value]!, - _selectedValue!.organization_metadata[0].value!, - _selectedValue!.organization_metadata[1].value!, - ); + _fetchedPersonData = await fetchPersons( + _selectedValue!.id!, avinyaTypeId[value]!); setState(() { _selectedAvinyaTypeId = value; this._isFetching = false; - _data = MyData(_fetchedDailyAttendanceSummaryData, - updateSelected); - filteredStudents = - _fetchedDailyAttendanceSummaryData; + _data = MyData( + _fetchedPersonData, updateSelected, context); + filteredStudents = _fetchedPersonData; }); }), SizedBox( @@ -344,9 +328,8 @@ class _StudentsState extends State { width: 25.0, height: 30.0, child: _selectedValue != null - ? AttendanceSummaryExcelReportExport( - fetchedDailyAttendanceSummaryData: - filteredStudents, + ? PersonDataExcelReport( + fetchedPersonData: filteredStudents, columnNames: columnNames, updateExcelState: updateExcelState, isFetching: _isFetching, @@ -374,7 +357,7 @@ class _StudentsState extends State { size: 50, // Customize the size of the indicator ), ) - else if (_fetchedDailyAttendanceSummaryData.length > 0) + else if (_fetchedPersonData.length > 0) ScrollConfiguration( behavior: ScrollConfiguration.of(context).copyWith(dragDevices: { @@ -406,9 +389,10 @@ class _StudentsState extends State { } class MyData extends DataTableSource { - MyData(this._fetchedDailyAttendanceSummaryData, this.updateSelected); + final BuildContext context; + MyData(this._fetchedPersonData, this.updateSelected, this.context); - final List _fetchedDailyAttendanceSummaryData; + final List _fetchedPersonData; final Function(int, bool, List) updateSelected; @override @@ -420,42 +404,51 @@ class MyData extends DataTableSource { ); } - if (_fetchedDailyAttendanceSummaryData.length > 0 && - index <= _fetchedDailyAttendanceSummaryData.length) { + if (_fetchedPersonData.length > 0 && index <= _fetchedPersonData.length) { List cells = List.filled(6, DataCell.empty); cells[0] = DataCell(Center( - child: Text( - _fetchedDailyAttendanceSummaryData[index - 1].sign_in_date!))); + child: Text(_fetchedPersonData[index - 1].preferred_name ?? "N/A"))); cells[1] = DataCell(Center( - child: Text(_fetchedDailyAttendanceSummaryData[index - 1] - .present_count! - .toString()))); + child: + Text(_fetchedPersonData[index - 1].nic_no?.toString() ?? "N/A"))); cells[2] = DataCell(Center( - child: Text(_fetchedDailyAttendanceSummaryData[index - 1] - .present_attendance_percentage! - .toString() + - " %"))); + child: Text(_fetchedPersonData[index - 1].date_of_birth?.toString() ?? + "N/A"))); cells[3] = DataCell(Center( - child: Text(_fetchedDailyAttendanceSummaryData[index - 1] - .late_count! - .toString()))); + child: Text( + _fetchedPersonData[index - 1].digital_id?.toString() ?? "N/A"))); - cells[4] = DataCell(Center( - child: Text(_fetchedDailyAttendanceSummaryData[index - 1] - .late_attendance_percentage! - .toString() + - " %"))); + cells[4] = DataCell( + Center(child: Text(_fetchedPersonData[index - 1].sex ?? "N/A"))); cells[5] = DataCell(Center( - child: Text(_fetchedDailyAttendanceSummaryData[index - 1] - .total_count - .toString()))); + child: Text( + _fetchedPersonData[index - 1].organization?.avinya_type?.name ?? + "N/A", + ), + )); - return DataRow(cells: cells); + // return DataRow(cells: cells); + return DataRow( + cells: cells, + onSelectChanged: (selected) { + if (selected != null && selected) { + // Navigate to the new screen with the id + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StudentUpdateScreen( + id: _fetchedPersonData[index - 1].id!, // Pass the ID + ), + ), + ); + } + }, + ); } return null; // Return null for invalid index values @@ -467,7 +460,7 @@ class MyData extends DataTableSource { @override int get rowCount { int count = 0; - count = _fetchedDailyAttendanceSummaryData.length + 1; + count = _fetchedPersonData.length + 1; return count; } diff --git a/campus/frontend/lib/config/app_config.dart b/campus/frontend/lib/config/app_config.dart index 04ac68bf..cf00c040 100644 --- a/campus/frontend/lib/config/app_config.dart +++ b/campus/frontend/lib/config/app_config.dart @@ -7,6 +7,7 @@ class AppConfig { static String campusAttendanceBffApiUrl = ''; static String campusProfileBffApiUrl = ''; static String campusAssetsBffApiUrl = ''; + static String campusEnrollmentsBffApiUrl = ''; static String campusBffApiKey = ''; static String refreshToken = ''; static String choreoSTSEndpoint = ""; @@ -44,6 +45,7 @@ class AppConfig { campusPctiNotesBffApiUrl = json['campusPctiNotesBffApiUrl']; campusPctiFeedbackBffApiUrl = json['campusPctiFeedbackBffApiUrl']; campusAssetsBffApiUrl = json['campusAssetsBffApiUrl']; + campusEnrollmentsBffApiUrl = json['campusEnrollmentsBffApiUrl']; choreoSTSEndpoint = json['choreo_sts_endpoint']; asgardeoTokenEndpoint = json['asgardeo_token_endpoint']; asgardeoLogoutUrl = json['logout_url']; From dc988d691ed9aad4e7203f6e76f5e7f7474372ba Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Thu, 26 Sep 2024 14:34:40 +0530 Subject: [PATCH 3/6] lint --- campus/bffs/enrollment/api/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/campus/bffs/enrollment/api/Dependencies.toml b/campus/bffs/enrollment/api/Dependencies.toml index e3ce285d..f7085f26 100644 --- a/campus/bffs/enrollment/api/Dependencies.toml +++ b/campus/bffs/enrollment/api/Dependencies.toml @@ -108,7 +108,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.15" +version = "2.10.16" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, From 8f3a1c72f34dc654caa8263e328498d07627986d Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Sun, 29 Sep 2024 17:06:25 +0530 Subject: [PATCH 4/6] student profile WIP --- .../assets/images/student_profile_male.jpg | Bin 0 -> 49752 bytes .../avinya/enrollment/lib/data/person.dart | 178 +++- .../lib/screens/student_update_screen.dart | 20 +- .../lib/widgets/student_update.dart | 847 +++++++++--------- .../enrollment/lib/widgets/students.dart | 5 - 5 files changed, 578 insertions(+), 472 deletions(-) create mode 100644 campus/frontend/assets/images/student_profile_male.jpg diff --git a/campus/frontend/assets/images/student_profile_male.jpg b/campus/frontend/assets/images/student_profile_male.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0f29c3725ac1dcdc681a0829bcc4620aed97847c GIT binary patch literal 49752 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 json) { + return City( + id: json['id'], + name: json['name']['name_en'], // Adjust as needed for localization + ); + } +} + +class Address { + String? record_type; + int? id; + String? name_en; + String? street_address; + int? phone; + int? city_id; + City? city; + + Address( + {this.id, + this.name_en, + this.street_address, + this.phone, + this.city_id, + this.record_type, + this.city}); + + factory Address.fromJson(Map json) { + return Address( + id: json['id'], + name_en: json['name_en'], + street_address: json['street_address'], + phone: json['phone'], + city_id: json['city_id'], + record_type: json['record_type'], + city: json['city'] != null ? City.fromJson(json['city']) : null, + ); + } + + Map toJson() => { + if (id != null) 'id': id, + if (name_en != null) 'name_en': name_en, + if (street_address != null) 'street_address': street_address, + if (phone != null) 'phone': phone, + if (city_id != null) 'city_id': city_id, + if (record_type != null) 'record_type': record_type, + if (city != null) 'city': city, + }; +} + class Person { int? id; String? record_type; @@ -93,44 +147,67 @@ class Person { int? avinya_type_id; String? passport_no; int? permanent_address_id; - String? digital_id; int? mailing_address_id; String? nic_no; String? id_no; int? phone; int? organization_id; + MainOrganization? organization; + AvinyaType? avinya_type; String? asgardeo_id; String? jwt_sub_id; String? jwt_email; String? email; Address? permanent_address; Address? mailing_address; - MainOrganization? organization; + String? street_address; + String? bank_account_number; + String? bank_name; + String? bank_branch; + String? digital_id; + String? bank_account_name; + int? avinya_phone; + int? academy_org_id; + String? created; + String? updated; + var parent_students = []; - Person( - {this.id, - this.record_type, - this.preferred_name, - this.full_name, - this.notes, - this.date_of_birth, - this.sex, - this.avinya_type_id, - this.passport_no, - this.permanent_address_id, - this.digital_id, - this.mailing_address_id, - this.nic_no, - this.id_no, - this.phone, - this.organization_id, - this.asgardeo_id, - this.jwt_sub_id, - this.jwt_email, - this.email, - this.permanent_address, - this.mailing_address, - this.organization}); + Person({ + this.id, + this.record_type, + this.preferred_name, + this.full_name, + this.notes, + this.date_of_birth, + this.sex, + this.avinya_type_id, + this.passport_no, + this.permanent_address_id, + this.mailing_address_id, + this.nic_no, + this.id_no, + this.phone, + this.organization_id, + this.organization, + this.avinya_type, + this.asgardeo_id, + this.jwt_sub_id, + this.jwt_email, + this.email, + this.permanent_address, + this.mailing_address, + this.street_address, + this.bank_account_number, + this.bank_name, + this.bank_branch, + this.digital_id, + this.bank_account_name, + this.avinya_phone, + this.academy_org_id, + this.created, + this.updated, + this.parent_students = const [], + }); factory Person.fromJson(Map json) { return Person( @@ -144,7 +221,6 @@ class Person { avinya_type_id: json['avinya_type_id'], passport_no: json['passport_no'], permanent_address_id: json['permanent_address_id'], - digital_id: json['digital_id'], mailing_address_id: json['mailing_address_id'], nic_no: json['nic_no'], id_no: json['id_no'], @@ -158,9 +234,25 @@ class Person { json['permanent_address'] != null ? json['permanent_address'] : {}), mailing_address: Address.fromJson( json['mailing_address'] != null ? json['mailing_address'] : {}), - organization: json['organization'] != null - ? MainOrganization.fromJson(json['organization']) - : null, + street_address: json['street_address'], + bank_account_number: json['bank_account_number'], + bank_name: json['bank_name'], + bank_branch: json['bank_branch'], + digital_id: json['digital_id'], + bank_account_name: json['bank_account_name'], + avinya_phone: json['avinya_phone'], + academy_org_id: json['academy_org_id'], + organization: MainOrganization.fromJson( + json['organization'] != null ? json['organization'] : {}), + avinya_type: AvinyaType.fromJson( + json['avinya_type'] != null ? json['avinya_type'] : {}), + created: json['created'], + updated: json['updated'], + parent_students: json['parent_students'] != null + ? json['parent_students'] + .map((eval_json) => Person.fromJson(eval_json)) + .toList() + : [], ); } @@ -176,7 +268,6 @@ class Person { if (passport_no != null) 'passport_no': passport_no, if (permanent_address_id != null) 'permanent_address_id': permanent_address_id, - if (digital_id != null) 'digital_id': digital_id, if (mailing_address_id != null) 'mailing_address_id': mailing_address_id, if (nic_no != null) 'nic_no': nic_no, @@ -191,8 +282,23 @@ class Person { 'permanent_address': permanent_address!.toJson(), if (mailing_address != null) 'mailing_address': mailing_address!.toJson(), + if (street_address != null) 'street_address': street_address, + if (bank_account_number != null) + 'bank_account_number': bank_account_number, + if (bank_name != null) 'bank_name': bank_name, + if (bank_branch != null) 'bank_name': bank_branch, + if (digital_id != null) 'digital_id': digital_id, + if (bank_account_name != null) 'bank_account_name': bank_account_name, + if (avinya_phone != null) 'avinya_phone': avinya_phone, + if (academy_org_id != null) 'academy_org_id': academy_org_id, if (organization != null) 'organization': organization!.toJson(), + if (avinya_type != null) 'avinya_type': avinya_type!.toJson(), + if (created != null) 'created': created, + if (updated != null) 'updated': updated, + 'parent_students': [parent_students], }; + + map(DataRow Function(dynamic evaluation) param0) {} } // Future> fetchPersons() async { @@ -236,10 +342,10 @@ Future> fetchPersons( } } -Future fetchPerson(String jwt_sub_id) async { +Future fetchPerson(int? person_id) async { final response = await http.get( Uri.parse( - AppConfig.campusAssetsBffApiUrl + '/student_applicant/$jwt_sub_id'), + AppConfig.campusEnrollmentsBffApiUrl + '/person_by_id/$person_id'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'accept': 'application/json', @@ -247,7 +353,7 @@ Future fetchPerson(String jwt_sub_id) async { }, ); - if (response.statusCode == 200) { + if (response.statusCode > 199 && response.statusCode < 300) { Person person = Person.fromJson(json.decode(response.body)); return person; } else { diff --git a/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart b/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart index 9edf965e..ba6b5e0b 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/screens/student_update_screen.dart @@ -15,25 +15,11 @@ class _StudentUpdateScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("Student Enrollment & Records", - style: TextStyle(color: Colors.black)), + title: Text("Student Profile", style: TextStyle(color: Colors.black)), backgroundColor: Color.fromARGB(255, 120, 224, 158), ), - body: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: - MediaQuery.of(context).size.height, // Ensure minimum height - ), - child: IntrinsicHeight( - child: Column( - children: [ - StudentUpdate(id: widget.id), // Pass the ID - // Add other widgets or buttons if needed - ], - ), - ), - ), + body: Container( + child: StudentUpdate(id: widget.id), ), ); } 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 61a6deb6..b4ed7692 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -1,31 +1,6 @@ -// import 'package:flutter/material.dart'; - -// class StudentUpdate extends StatefulWidget { -// final int? id; -// const StudentUpdate({Key? key, this.id}) : super(key: key); - -// @override -// State createState() => _StudentUpdateState(); -// } - -// class _StudentUpdateState extends State { -// @override -// Widget build(BuildContext context) { -// return Column( -// children: [ -// Text('Details for student with ID: ${widget.id}'), -// // Add more widgets related to student details here -// ], -// ); -// } -// } - -import 'package:gallery/constants.dart'; import 'package:flutter/material.dart'; -import 'package:gallery/data/campus_apps_portal.dart'; -import 'package:gallery/data/person.dart'; -import 'package:sizer/sizer.dart'; import 'package:intl/intl.dart'; +import 'package:gallery/avinya/enrollment/lib/data/person.dart'; class StudentUpdate extends StatefulWidget { final int? id; @@ -36,9 +11,14 @@ class StudentUpdate extends StatefulWidget { } class _StudentUpdateState extends State { - late Person userPerson = Person() - ..full_name = 'John' - ..nic_no = '12'; + late Person userPerson = Person(); + final GlobalKey _formKey = GlobalKey(); + + String? selectedSex; + int? selectedCityId; + int? selectedOrgId; + int? selectedClassId; + DateTime? selectedDateOfBirth; @override void initState() { @@ -46,425 +26,464 @@ class _StudentUpdateState extends State { getUserPerson(); } - void getUserPerson() { - // Retrieve user data from local instance - Person user = campusAppsPortalInstance.getUserPerson(); + Future getUserPerson() async { + Person user = await fetchPerson(widget.id); setState(() { userPerson = user; + selectedSex = userPerson.sex; + selectedCityId = userPerson.mailing_address?.city?.id; + selectedOrgId = userPerson.organization?.id; + selectedClassId = userPerson.organization?.id; + selectedDateOfBirth = DateTime.tryParse(userPerson.date_of_birth ?? ''); }); } - int calculateAge(String dateOfBirth) { - DateTime today = DateTime.now(); - DateTime dob = DateTime.parse(dateOfBirth); - int age = today.year - dob.year; - if (today.month < dob.month || - (today.month == dob.month && today.day < dob.day)) { - age--; - } - return age; - } - @override Widget build(BuildContext context) { return Scaffold( - body: SingleChildScrollView( - child: Container( - color: kOtherColor, - child: Column( - children: [ - sizedBox, - Container( - width: 100.w, - height: SizerUtil.deviceType == DeviceType.tablet ? 19.h : 15.h, - decoration: BoxDecoration( - color: kPrimaryColor, - borderRadius: kBottomBorderRadius, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircleAvatar( - radius: SizerUtil.deviceType == DeviceType.tablet - ? 12.w - : 13.w, - backgroundColor: kSecondaryColor, - backgroundImage: - AssetImage('assets/images/student_profile.jpeg'), - ), - kWidthSizedBox, - Column( + body: userPerson.full_name == null + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 800, + child: Form( + key: _formKey, + child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '${userPerson.full_name == null ? 'N/A' : userPerson.full_name!}', - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == - DeviceType.mobile - ? 16.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 15.sp - : SizerUtil.deviceType == DeviceType.web - ? 7.sp - : 12.sp, - ), - ), - Text( - '${userPerson.organization == null ? 'N/A' : userPerson.organization!.name == null ? 'N/A' : userPerson.organization!.name!.name_en == null ? 'N/A' : userPerson.organization!.name!.name_en!}', - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == - DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), + _buildProfileHeader(context), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Student Information'), + _buildEditableField( + 'Preferred Name', userPerson.preferred_name, + (value) { + userPerson.preferred_name = value; + }), + _buildEditableField('Full Name', userPerson.full_name, + (value) { + userPerson.full_name = value; + }), + _buildDateOfBirthField(context), + _buildSexField(), + const SizedBox(height: 10), + _buildStudentClassField(), // Student Class based on organization.description + const SizedBox(height: 20), + _buildSectionTitle(context, 'Contact Information'), + _buildEditableField('Personal Email', userPerson.email, + (value) { + userPerson.email = value; + }), + _buildEditableField( + 'Phone', userPerson.phone?.toString() ?? '', + (value) { + userPerson.phone = int.tryParse(value); + }), + _buildEditableField('Street Address', + userPerson.mailing_address?.street_address ?? 'N/A', + (value) { + if (userPerson.mailing_address == null) { + userPerson.mailing_address = + Address(street_address: value); + } else { + userPerson.mailing_address!.street_address = value; + } + }), + _buildCityField(), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Digital Information'), + _buildEditableField('Digital ID', userPerson.digital_id, + (value) { + userPerson.digital_id = value; + }), + _buildEditableField('Avinya Type', + userPerson.avinya_type?.name ?? 'N/A', (value) { + userPerson.avinya_type?.name = value; + }), + _buildOrganizationField(), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Bank Information'), + _buildEditableField('Bank Name', userPerson.bank_name, + (value) { + userPerson.bank_name = value; + }), + _buildEditableField( + 'Bank Branch', userPerson.bank_branch, (value) { + userPerson.bank_branch = value; + }), + _buildEditableField( + 'Bank Account Name', userPerson.bank_account_name, + (value) { + userPerson.bank_account_name = value; + }), + _buildEditableField( + 'Account Number', userPerson.bank_account_number, + (value) { + userPerson.bank_account_number = value; + }), + const SizedBox(height: 40), + _buildSaveButton(), ], - ) - ], - ), - ), - sizedBox, - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ProfileDetailRow( - title: 'Registration Number', - value: - '${userPerson.id == null ? 'N/A' : userPerson.id}'), - ProfileDetailRow( - title: 'Academic Year', - value: - '${userPerson.updated == null ? 'N/A' : '${DateFormat('yyyy').format(DateTime.parse(userPerson.updated!))} - ${DateFormat('yyyy').format(DateTime.parse(userPerson.updated!).add(Duration(days: 365)))} '}'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ProfileDetailRow( - title: 'Programme', - value: - '${userPerson.avinya_type == null ? 'N/A' : userPerson.avinya_type!.focus == null ? 'N/A' : userPerson.avinya_type!.focus}', - ), - ProfileDetailRow(title: 'Class', value: 'TBD'), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ProfileDetailRow( - title: 'Date of Admission', - value: - '${userPerson.created == null ? 'N/A' : DateFormat('d MMM, yyyy').format(DateTime.parse(userPerson.created!))}'), - // ProfileDetailRow( - // title: 'Age', - // value: - // '${calculateAge(userPerson.date_of_birth!)} years old'), - ], - ), - sizedBox, - sizedBox, - sizedBox, - Text( - 'Student Information', - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontWeight: FontWeight.bold, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - sizedBox, - ProfileDetailColumn( - title: 'Full Name', - value: - '${userPerson.full_name == null ? 'N/A' : userPerson.full_name}', - ), - ProfileDetailColumn( - title: 'Preferred Name', - value: - '${userPerson.preferred_name == null ? 'N/A' : userPerson.preferred_name}', - ), - ProfileDetailColumn( - title: 'Date of Birth', - value: - '${userPerson.date_of_birth == null ? 'N/A' : DateFormat('d MMM, yyyy').format(DateTime.parse(userPerson.date_of_birth!))}', - ), - ProfileDetailColumn( - title: 'Gender', - value: '${userPerson.sex == null ? 'N/A' : userPerson.sex}', - ), - ProfileDetailColumn( - title: 'NIC Number', - value: - '${userPerson.nic_no == null ? 'N/A' : userPerson.nic_no}', - ), - ProfileDetailColumn( - title: 'Passport Number', - value: - '${userPerson.passport_no == null ? 'N/A' : userPerson.passport_no}', - ), - sizedBox, - sizedBox, - sizedBox, - Text( - 'Student Contact Information', - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontWeight: FontWeight.bold, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - sizedBox, - ProfileDetailColumn( - title: 'Personal Phone Number', - value: '${userPerson.phone == null ? 'N/A' : userPerson.phone}', - ), - ProfileDetailColumn( - title: 'Avinya Phone Number', - value: - '${userPerson.avinya_phone == null ? 'N/A' : userPerson.avinya_phone}', - ), - ProfileDetailColumn( - title: 'Email', - value: '${userPerson.email == null ? 'N/A' : userPerson.email}', - ), - ProfileDetailColumn( - title: 'Home Address', - value: - '${userPerson.permanent_address == null ? 'N/A' : userPerson.permanent_address!.street_address == null ? 'N/A' : userPerson.permanent_address!.street_address}', - ), - ProfileDetailColumn( - title: 'Mailing Address', - value: - '${userPerson.mailing_address == null ? 'N/A' : userPerson.mailing_address!.street_address == null ? 'N/A' : userPerson.mailing_address!.street_address}', - ), - sizedBox, - sizedBox, - sizedBox, - Text( - 'Student Bank Information', - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontWeight: FontWeight.bold, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, ), + ), + ), ), - sizedBox, - ProfileDetailColumn( - title: 'Bank Name', - value: - '${userPerson.bank_name == null ? 'N/A' : userPerson.bank_name}', - ), - ProfileDetailColumn( - title: 'Bank Account Name', - value: - '${userPerson.bank_account_name == null ? 'N/A' : userPerson.bank_account_name}', - ), - ProfileDetailColumn( - title: 'Bank Account Number', - value: - '${userPerson.bank_account_number == null ? 'N/A' : userPerson.bank_account_number}', - ), - sizedBox, - sizedBox, - sizedBox, - Text( - 'Parent/Guardian Information', - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontWeight: FontWeight.bold, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - sizedBox, - ProfileDetailColumn( - title: 'Father Name', - value: - '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[0].preferred_name != null ? userPerson.parent_students[0].preferred_name : 'N/A'}', - ), - ProfileDetailColumn( - title: 'Mother Name', - value: - '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[1].preferred_name != null ? userPerson.parent_students[1].preferred_name : 'N/A'}', - ), - ProfileDetailColumn( - title: 'Father Phone Number', - value: - '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[0].phone != null ? userPerson.parent_students[0].phone : 'N/A'}', - ), - ProfileDetailColumn( - title: 'Mother Phone Number', - value: - '${userPerson.parent_students != null && userPerson.parent_students.isNotEmpty && userPerson.parent_students[1].phone != null ? userPerson.parent_students[1].phone : 'N/A'}', + ), + ); + } + + Widget _buildProfileHeader(BuildContext context) { + String imagePath = selectedSex == 'Male' + ? 'assets/images/student_profile_male.jpg' // Replace with the male profile image path + : 'assets/images/student_profile.jpeg'; // Default or female profile image + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + radius: 40, + backgroundImage: AssetImage(imagePath), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + userPerson.full_name ?? 'N/A', + style: Theme.of(context).textTheme.headline6, + ), + Text( + userPerson.organization?.name?.nameEn ?? 'N/A', + style: Theme.of(context).textTheme.subtitle1, + ), + ], + ), + ], + ); + } + + Widget _buildSectionTitle(BuildContext context, String title) { + return Text( + title, + style: Theme.of(context) + .textTheme + .subtitle1! + .copyWith(fontWeight: FontWeight.bold), + ); + } + + Widget _buildEditableField( + String label, String? initialValue, Function(String) onSave) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + label, + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: TextFormField( + initialValue: initialValue ?? '', + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + border: OutlineInputBorder(), ), - sizedBox, - sizedBox, - sizedBox, - ], + onSaved: (value) => onSave(value!), + ), ), - ), + ], ), ); } -} -class ProfileDetailRow extends StatelessWidget { - const ProfileDetailRow({Key? key, required this.title, required this.value}) - : super(key: key); - final String title; - final String value; - @override - Widget build(BuildContext context) { - return Container( - width: 40.w, + Widget _buildSexField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - kHalfSizedBox, - Text( - value, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), + Expanded( + flex: 4, + child: Text( + 'Sex', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedSex, + items: ['Male', 'Female', 'Other'] + .map((sex) => DropdownMenuItem(value: sex, child: Text(sex))) + .toList(), + onChanged: (value) { + setState(() { + selectedSex = value; + userPerson.sex = value; + }); + }, + decoration: const InputDecoration( + border: OutlineInputBorder(), ), - kHalfSizedBox, - SizedBox( - width: 35.w, - child: Divider( - thickness: 1.0, - color: kTextBlackColor, - ), + ), + ), + ], + ), + ); + } + + Widget _buildCityField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'City', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedCityId, + items: _getCityOptions(), + onChanged: (value) { + setState(() { + selectedCityId = value; + userPerson.mailing_address?.city_id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select City', + border: OutlineInputBorder(), ), - ], + ), ), - Icon( - Icons.lock_outline, - size: 6.sp, - color: kTextBlackColor, + ], + ), + ); + } + + Widget _buildOrganizationField() { + // Example list of organizations + List> organizations = [ + {'id': 1, 'name': 'Org 1'}, + {'id': 2, 'name': 'Org 2'}, + {'id': 18, 'name': 'Bandaragama - Group 1'}, // Make sure this is included + ]; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Main Organization', + style: Theme.of(context).textTheme.bodyText1, + ), ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: userPerson.organization + ?.id, // Set the initial value to match one in the list + items: organizations.map((org) { + return DropdownMenuItem( + value: org['id'], + child: Text(org['name']), + ); + }).toList(), + onChanged: (int? newValue) { + setState(() { + userPerson.organization_id = + newValue; // Update the organization ID + }); + }, + decoration: InputDecoration( + labelText: 'Select Organization', + border: OutlineInputBorder(), + ), + )), ], ), ); } -} -class ProfileDetailColumn extends StatelessWidget { - const ProfileDetailColumn( - {Key? key, required this.title, required this.value}) - : super(key: key); - final String title; - final String value; - @override - Widget build(BuildContext context) { - return Container( + String _formatDate(String? date) { + if (date == null) return 'N/A'; + return DateFormat('d MMM, yyyy').format(DateTime.parse(date)); + } + + List> _getCityOptions() { + // You can replace this with the actual data source + List> cities = [ + { + 'id': 690, + 'name': {'name_en': 'Horana', 'name_si': 'හොරණ', 'name_ta': 'ஹொரன'} + }, + { + 'id': 1, + 'name': {'name_en': 'Colombo', 'name_si': 'කොළඹ', 'name_ta': 'கொலம்பு'} + }, + { + 'id': 2, + 'name': {'name_en': 'Galle', 'name_si': 'ගාලු', 'name_ta': 'கல்லி'} + }, + { + 'id': 3, + 'name': {'name_en': 'Kandy', 'name_si': 'කාන්ඩි', 'name_ta': 'காண்டி'} + }, + { + 'id': 4, + 'name': { + 'name_en': 'Jaffna', + 'name_si': 'යාපනය', + 'name_ta': 'யாழ்ப்பாணம்' + } + }, + ]; + + return cities.map((city) { + return DropdownMenuItem( + value: city['id'] as int, // Explicitly cast to int + child: Text(city['name']['name_en'] + as String), // Use the English name or adjust as needed + ); + }).toList(); + } + + List> _getClassOptions() { + List> classes = [ + {'id': 18, 'description': 'Leopards'}, + {'id': 2, 'description': 'Dolphine'}, + {'id': 3, 'description': 'Bees'}, + {'id': 4, 'description': 'Elephents'}, + ]; + + return classes + .map((classe) => DropdownMenuItem( + value: classe['id'] as int, // Explicitly cast to int + child: Text( + classe['description'] as String), // Explicitly cast to String + )) + .toList(); + } + + List> _getOrganizationOptions() { + List> organizations = [ + {'id': 1, 'name': 'Avinya Academy - Colombo'}, + {'id': 2, 'name': 'Avinya Academy - Galle'}, + {'id': 3, 'name': 'Avinya Academy - Jaffna'}, + ]; + + return organizations + .map((org) => DropdownMenuItem( + value: org['id'] as int, + child: Text(org['name'] as String), + )) + .toList(); + } + + Widget _buildSaveButton() { + return Center( + child: ElevatedButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + // Save userPerson changes + // savePerson(userPerson); + } + }, + child: const Text('Save Changes'), + ), + ); + } + + Widget _buildDateOfBirthField(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - kHalfSizedBox, - Text( - value, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: kTextBlackColor, - fontSize: SizerUtil.deviceType == DeviceType.mobile - ? 15.sp - : SizerUtil.deviceType == DeviceType.tablet - ? 14.sp - : SizerUtil.deviceType == DeviceType.web - ? 6.sp - : 11.sp, - ), - ), - kHalfSizedBox, - SizedBox( - width: 92.w, - child: Divider( - thickness: 1.0, - color: kTextBlackColor, + Expanded( + flex: 4, + child: Text( + 'Date of Birth', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: InkWell( + onTap: () async { + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: selectedDateOfBirth ?? DateTime(2000), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (pickedDate != null) { + setState(() { + selectedDateOfBirth = pickedDate; + userPerson.date_of_birth = + DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + }, + child: InputDecorator( + decoration: const InputDecoration( + labelText: 'Select Date Of Birth', + border: OutlineInputBorder(), + ), + child: Text( + selectedDateOfBirth == null + ? 'Select Date' + : DateFormat('d MMM, yyyy').format(selectedDateOfBirth!), ), - ) - ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildStudentClassField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Class', + style: Theme.of(context).textTheme.bodyText1, + ), ), - Icon( - Icons.lock_outline, - size: 6.sp, - color: kTextBlackColor, + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedClassId, + items: _getClassOptions(), + onChanged: (value) { + setState(() { + selectedClassId = value; + userPerson.organization?.id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select Class', + border: OutlineInputBorder(), + ), + ), ), ], ), diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index b5c2b40f..9b098fd4 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -1,15 +1,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:attendance/data/activity_attendance.dart'; import 'package:attendance/data/organization.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:gallery/avinya/enrollment/lib/data/person.dart'; import 'package:gallery/avinya/enrollment/lib/screens/student_update_screen.dart'; -import 'package:intl/intl.dart'; -import 'package:gallery/data/campus_apps_portal.dart'; -import 'package:attendance/widgets/date_range_picker.dart'; - import 'person_data_excel_report.dart'; enum AvinyaTypeId { Empower, IT, CS } From 3033228e046c1be3d1f10819fab115dcad358a43 Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Mon, 30 Sep 2024 11:27:24 +0530 Subject: [PATCH 5/6] student profile WIP --- .../avinya/enrollment/lib/data/person.dart | 193 ++++++++++++++-- .../lib/widgets/student_update.dart | 206 ++++++++++++------ 2 files changed, 310 insertions(+), 89 deletions(-) diff --git a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart index ed5fdbce..92c99073 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart @@ -10,29 +10,32 @@ class MainOrganization { int? id; String? description; String? notes; - String? address; + Address? address; AvinyaType? avinya_type; Name? name; + String? phone; - MainOrganization({ - this.id, - this.description, - this.notes, - this.address, - this.avinya_type, - this.name, - }); + MainOrganization( + {this.id, + this.description, + this.notes, + this.address, + this.avinya_type, + this.name, + this.phone}); factory MainOrganization.fromJson(Map json) { return MainOrganization( id: json['id'], description: json['description'], notes: json['notes'], - address: json['address'], + address: + json['address'] != null ? Address.fromJson(json['address']) : null, avinya_type: json['avinya_type'] != null ? AvinyaType.fromJson(json['avinya_type']) : null, name: json['name'] != null ? Name.fromJson(json['name']) : null, + phone: json['phone'], ); } @@ -40,59 +43,88 @@ class MainOrganization { if (id != null) 'id': id, if (description != null) 'description': description, if (notes != null) 'notes': notes, - if (address != null) 'address': address, + if (address != null) 'address': address!.toJson(), if (avinya_type != null) 'avinya_type': avinya_type!.toJson(), if (name != null) 'name': name!.toJson(), + if (phone != null) 'phone': phone, }; } class AvinyaType { int? id; String? name; + int? level; + bool? active; + String? foundationType; + String? focus; + String? globalType; - AvinyaType({this.id, this.name}); + AvinyaType({ + this.id, + this.name, + this.level, + this.active, + this.foundationType, + this.focus, + this.globalType, + }); factory AvinyaType.fromJson(Map json) { return AvinyaType( id: json['id'], name: json['name'], + level: json['level'], + active: json['active'], + foundationType: json['foundation_type'], + focus: json['focus'], + globalType: json['global_type'], ); } Map toJson() => { if (id != null) 'id': id, if (name != null) 'name': name, + if (level != null) 'level': level, + if (active != null) 'active': active, + if (foundationType != null) 'foundation_type': foundationType, + if (focus != null) 'focus': focus, + if (globalType != null) 'global_type': globalType, }; } class Name { - String? nameEn; + String? name_en; - Name({this.nameEn}); + Name({this.name_en}); factory Name.fromJson(Map json) { return Name( - nameEn: json['name_en'], + name_en: json['name_en'], ); } Map toJson() => { - if (nameEn != null) 'name_en': nameEn, + if (name_en != null) 'name_en': name_en, }; } class City { - final int id; - final String name; + int? id; + Name? name; - City({required this.id, required this.name}); + City({this.id, this.name}); factory City.fromJson(Map json) { return City( id: json['id'], - name: json['name']['name_en'], // Adjust as needed for localization + name: Name.fromJson(json['name']), ); } + + Map toJson() => { + if (id != null) 'id': id, + if (name != null) 'name': name?.toJson(), + }; } class Address { @@ -102,7 +134,9 @@ class Address { String? street_address; int? phone; int? city_id; + int? district_id; City? city; + District? district; Address( {this.id, @@ -110,8 +144,10 @@ class Address { this.street_address, this.phone, this.city_id, + this.district_id, this.record_type, - this.city}); + this.city, + this.district}); factory Address.fromJson(Map json) { return Address( @@ -120,8 +156,11 @@ class Address { street_address: json['street_address'], phone: json['phone'], city_id: json['city_id'], + district_id: json['district_id'], record_type: json['record_type'], city: json['city'] != null ? City.fromJson(json['city']) : null, + district: + json['district'] != null ? District.fromJson(json['district']) : null, ); } @@ -131,8 +170,10 @@ class Address { if (street_address != null) 'street_address': street_address, if (phone != null) 'phone': phone, if (city_id != null) 'city_id': city_id, + if (district_id != null) 'district_id': district_id, if (record_type != null) 'record_type': record_type, if (city != null) 'city': city, + if (district != null) 'district': district, }; } @@ -301,6 +342,52 @@ class Person { map(DataRow Function(dynamic evaluation) param0) {} } +class District { + int? id; + Province? province; + List? cities; + Name? name; + + District({this.id, this.province, this.cities, this.name}); + + 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']), + ); + } + + 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(), + }; +} + +class Province { + int? id; + Name? name; + + Province({this.id, this.name}); + + factory Province.fromJson(Map json) { + return Province( + id: json['id'], + name: Name.fromJson(json['name']), + ); + } + + Map toJson() => { + if (id != null) 'id': id, + if (name != null) 'name': name?.toJson(), + }; +} + // Future> fetchPersons() async { // final response = await http.get( // Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), @@ -363,7 +450,7 @@ Future fetchPerson(int? person_id) async { Future createPerson(Person person) async { final response = await http.post( - Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), + Uri.parse(AppConfig.campusEnrollmentsBffApiUrl + '/student_applicant'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, @@ -382,7 +469,7 @@ Future createPerson(Person person) async { Future updatePerson(Person person) async { final response = await http.put( - Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), + Uri.parse(AppConfig.campusEnrollmentsBffApiUrl + '/update_person'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, @@ -411,3 +498,63 @@ Future deletePerson(String id) async { throw Exception('Failed to delete Person.'); } } + +Future> fetchAvinyaTypes() async { + final response = await http.get( + Uri.parse('${AppConfig.campusAttendanceBffApiUrl}/avinya_types'), + 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) => AvinyaType.fromJson(json)) + .toList(); + return activityAttendances; + } else { + throw Exception('Failed to get AvinyaType Data'); + } +} + +Future> fetchOrganizations() async { + final response = await http.get( + Uri.parse('${AppConfig.campusEnrollmentsBffApiUrl}/all_organizations'), + 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) => MainOrganization.fromJson(json)) + .toList(); + return activityAttendances; + } else { + throw Exception('Failed to get Org Data'); + } +} + +Future> fetchDistricts() async { + final response = await http.get( + Uri.parse('${AppConfig.campusEnrollmentsBffApiUrl}/districts'), + 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) => District.fromJson(json)) + .toList(); + return activityAttendances; + } else { + throw Exception('Failed to get Org Data'); + } +} 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 b4ed7692..d757c34c 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -12,10 +12,14 @@ class StudentUpdate extends StatefulWidget { class _StudentUpdateState extends State { late Person userPerson = Person(); + List districts = []; + List organizations = []; + List avinyaTypes = []; final GlobalKey _formKey = GlobalKey(); String? selectedSex; int? selectedCityId; + int? selectedDistrictId; int? selectedOrgId; int? selectedClassId; DateTime? selectedDateOfBirth; @@ -23,7 +27,14 @@ class _StudentUpdateState extends State { @override void initState() { super.initState(); - getUserPerson(); + loadData(); + } + + Future loadData() async { + await getUserPerson(); + await fetchDistrictList(); + await fetchOrganizationList(); + await fetchAvinyaTypeList(); } Future getUserPerson() async { @@ -32,12 +43,34 @@ class _StudentUpdateState extends State { userPerson = user; selectedSex = userPerson.sex; selectedCityId = userPerson.mailing_address?.city?.id; + selectedDistrictId = userPerson.mailing_address?.district?.id; selectedOrgId = userPerson.organization?.id; selectedClassId = userPerson.organization?.id; selectedDateOfBirth = DateTime.tryParse(userPerson.date_of_birth ?? ''); }); } + Future fetchDistrictList() async { + List districtList = await fetchDistricts(); + setState(() { + districts = districtList; + }); + } + + Future fetchOrganizationList() async { + List orgList = await fetchOrganizations(); + setState(() { + organizations = orgList; + }); + } + + Future fetchAvinyaTypeList() async { + List avinyaTypeList = await fetchAvinyaTypes(); + setState(() { + avinyaTypes = avinyaTypeList; + }); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -90,6 +123,7 @@ class _StudentUpdateState extends State { userPerson.mailing_address!.street_address = value; } }), + _buildDistrictField(), _buildCityField(), const SizedBox(height: 20), _buildSectionTitle(context, 'Digital Information'), @@ -97,10 +131,7 @@ class _StudentUpdateState extends State { (value) { userPerson.digital_id = value; }), - _buildEditableField('Avinya Type', - userPerson.avinya_type?.name ?? 'N/A', (value) { - userPerson.avinya_type?.name = value; - }), + _buildAvinyaTypeField(), _buildOrganizationField(), const SizedBox(height: 20), _buildSectionTitle(context, 'Bank Information'), @@ -154,7 +185,7 @@ class _StudentUpdateState extends State { style: Theme.of(context).textTheme.headline6, ), Text( - userPerson.organization?.name?.nameEn ?? 'N/A', + userPerson.organization?.name?.name_en ?? 'N/A', style: Theme.of(context).textTheme.subtitle1, ), ], @@ -238,6 +269,40 @@ class _StudentUpdateState extends State { ); } + Widget _buildDistrictField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'District', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedDistrictId, + items: _getDistrictOptions(), + onChanged: (value) { + setState(() { + selectedDistrictId = value; + userPerson.mailing_address?.district_id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select District', + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + ); + } + Widget _buildCityField() { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -253,8 +318,12 @@ class _StudentUpdateState extends State { Expanded( flex: 6, child: DropdownButtonFormField( - value: selectedCityId, - items: _getCityOptions(), + value: selectedCityId != null && + _getCityOptions() + .any((item) => item.value == selectedCityId) + ? selectedCityId + : null, // Set value to null if the selectedCityId is not in the list + items: _getCityOptions(), // Pass the city options onChanged: (value) { setState(() { selectedCityId = value; @@ -273,13 +342,6 @@ class _StudentUpdateState extends State { } Widget _buildOrganizationField() { - // Example list of organizations - List> organizations = [ - {'id': 1, 'name': 'Org 1'}, - {'id': 2, 'name': 'Org 2'}, - {'id': 18, 'name': 'Bandaragama - Group 1'}, // Make sure this is included - ]; - return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( @@ -298,8 +360,8 @@ class _StudentUpdateState extends State { ?.id, // Set the initial value to match one in the list items: organizations.map((org) { return DropdownMenuItem( - value: org['id'], - child: Text(org['name']), + value: org.id, + child: Text(org.name?.name_en ?? 'Unknown'), ); }).toList(), onChanged: (int? newValue) { @@ -318,49 +380,76 @@ class _StudentUpdateState extends State { ); } + Widget _buildAvinyaTypeField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Avinya Type', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: userPerson.avinya_type_id, + items: avinyaTypes.map((org) { + return DropdownMenuItem( + value: org.id, + child: Text(org.name ?? 'Unknown'), + ); + }).toList(), + onChanged: (int? newValue) { + setState(() { + userPerson.avinya_type_id = + newValue; // Update the organization ID + }); + }, + decoration: InputDecoration( + labelText: 'Select Avinya Type', + border: OutlineInputBorder(), + ), + )), + ], + ), + ); + } + String _formatDate(String? date) { if (date == null) return 'N/A'; return DateFormat('d MMM, yyyy').format(DateTime.parse(date)); } - List> _getCityOptions() { - // You can replace this with the actual data source - List> cities = [ - { - 'id': 690, - 'name': {'name_en': 'Horana', 'name_si': 'හොරණ', 'name_ta': 'ஹொரன'} - }, - { - 'id': 1, - 'name': {'name_en': 'Colombo', 'name_si': 'කොළඹ', 'name_ta': 'கொலம்பு'} - }, - { - 'id': 2, - 'name': {'name_en': 'Galle', 'name_si': 'ගාලු', 'name_ta': 'கல்லி'} - }, - { - 'id': 3, - 'name': {'name_en': 'Kandy', 'name_si': 'කාන්ඩි', 'name_ta': 'காண்டி'} - }, - { - 'id': 4, - 'name': { - 'name_en': 'Jaffna', - 'name_si': 'යාපනය', - 'name_ta': 'யாழ்ப்பாணம்' - } - }, - ]; - - return cities.map((city) { + List> _getDistrictOptions() { + return districts.map((district) { return DropdownMenuItem( - value: city['id'] as int, // Explicitly cast to int - child: Text(city['name']['name_en'] - as String), // Use the English name or adjust as needed + value: district.id as int, + child: Text(district.name?.name_en ?? 'Unknown'), ); }).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() { List> classes = [ {'id': 18, 'description': 'Leopards'}, @@ -378,21 +467,6 @@ class _StudentUpdateState extends State { .toList(); } - List> _getOrganizationOptions() { - List> organizations = [ - {'id': 1, 'name': 'Avinya Academy - Colombo'}, - {'id': 2, 'name': 'Avinya Academy - Galle'}, - {'id': 3, 'name': 'Avinya Academy - Jaffna'}, - ]; - - return organizations - .map((org) => DropdownMenuItem( - value: org['id'] as int, - child: Text(org['name'] as String), - )) - .toList(); - } - Widget _buildSaveButton() { return Center( child: ElevatedButton( @@ -400,7 +474,7 @@ class _StudentUpdateState extends State { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); // Save userPerson changes - // savePerson(userPerson); + updatePerson(userPerson); } }, child: const Text('Save Changes'), From c9fe56bcce449eab16ce3d7e405bad7ef9e33d4f Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Mon, 30 Sep 2024 15:23:00 +0530 Subject: [PATCH 6/6] bal versioon updated --- campus/bffs/enrollment/api/Ballerina.toml | 4 +-- .../enrollment/lib/widgets/students.dart | 31 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/campus/bffs/enrollment/api/Ballerina.toml b/campus/bffs/enrollment/api/Ballerina.toml index 84d6d790..31346592 100644 --- a/campus/bffs/enrollment/api/Ballerina.toml +++ b/campus/bffs/enrollment/api/Ballerina.toml @@ -1,8 +1,8 @@ [package] org = "avinyafoundation" name = "enrollment_bff" -version = "1.1.0" -distribution = "2201.5.0" +version = "2.1.0" +distribution = "2201.8.7" [build-options] observabilityIncluded = true diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index 9b098fd4..227c557f 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -7,12 +7,13 @@ import 'package:gallery/avinya/enrollment/lib/data/person.dart'; import 'package:gallery/avinya/enrollment/lib/screens/student_update_screen.dart'; import 'person_data_excel_report.dart'; -enum AvinyaTypeId { Empower, IT, CS } +enum AvinyaTypeId { Empower, IT, CS, FutureEnrollees } const avinyaTypeId = { AvinyaTypeId.Empower: 37, AvinyaTypeId.IT: 10, - AvinyaTypeId.CS: 96 + AvinyaTypeId.CS: 96, + AvinyaTypeId.FutureEnrollees: 103, }; class Students extends StatefulWidget { @@ -34,7 +35,8 @@ class _StudentsState extends State { List filteredAvinyaTypeIdValues = [ AvinyaTypeId.Empower, AvinyaTypeId.IT, - AvinyaTypeId.CS + AvinyaTypeId.CS, + AvinyaTypeId.FutureEnrollees ]; List columnNames = []; @@ -129,19 +131,24 @@ class _StudentsState extends State { if (query.isEmpty) { filteredStudents = _fetchedPersonData; } else { + final lowerCaseQuery = query.toLowerCase(); + filteredStudents = _fetchedPersonData.where((student) { print('Searching for: $query'); print('Present count: ${student.preferred_name}'); - print('Attendance percentage: ${student.nic_no}'); + print('NIC number: ${student.nic_no}'); - final lowerCaseQuery = query.toLowerCase(); - final presentCountString = student.preferred_name?.toString() ?? ''; + // Ensure preferred_name is not null and trimmed + final presentCountString = + student.preferred_name?.trim().toLowerCase() ?? ''; final attendancePercentageString = student.nic_no?.toString() ?? ''; + // Check for matching query return presentCountString.contains(lowerCaseQuery) || attendancePercentageString.contains(lowerCaseQuery); }).toList(); } + _data = MyData(filteredStudents, updateSelected, context); }); } @@ -209,13 +216,15 @@ class _StudentsState extends State { .toString()) .isBefore(DateTime.parse('2024-03-01'))) { filteredAvinyaTypeIdValues = [ - AvinyaTypeId.Empower + AvinyaTypeId.Empower, + AvinyaTypeId.FutureEnrollees ]; } else { filteredAvinyaTypeIdValues = [ AvinyaTypeId.Empower, AvinyaTypeId.IT, - AvinyaTypeId.CS + AvinyaTypeId.CS, + AvinyaTypeId.FutureEnrollees ]; } @@ -262,7 +271,11 @@ class _StudentsState extends State { .map( (typeId) => DropdownMenuItem( value: typeId, - child: Text(typeId.name.toUpperCase()), + child: Text( + typeId.name == 'FutureEnrollees' + ? 'FUTURE ENROLLEES' + : typeId.name.toUpperCase(), + ), ), ) .toList(),