diff --git a/campus/bffs/asset/api/client.bal b/campus/bffs/asset/api/client.bal index 5c487d4c..bbe4ee33 100644 --- a/campus/bffs/asset/api/client.bal +++ b/campus/bffs/asset/api/client.bal @@ -231,12 +231,12 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetAvinyaTypesResponse); } - + remote isolated function getInventoryDataByOrganization(string date, int organization_id) returns GetInventoryDataByOrganizationResponse|graphql:ClientError { - string query = string `query getInventoryDataByOrganization($organization_id:Int!,$date:String!) {inventory_data_by_organization(organization_id:$organization_id,date:$date) {id avinya_type_id consumable_id quantity quantity_in quantity_out resource_property {id property value}}}`; + string query = string `query getInventoryDataByOrganization($organization_id:Int!,$date:String!) {inventory_data_by_organization(organization_id:$organization_id,date:$date) {id avinya_type_id consumable_id consumable {id name} quantity quantity_in quantity_out resource_property {id property value}}}`; map variables = {"date": date, "organization_id": organization_id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); - return check performDataBinding(graphqlResponse, GetInventoryDataByOrganizationResponse); + return check performDataBinding(graphqlResponse, GetInventoryDataByOrganizationResponse); } remote isolated function consumableReplenishment(int person_id,int organization_id,string date,Inventory[] inventories) returns json|graphql:ClientError { diff --git a/campus/bffs/asset/api/types.bal b/campus/bffs/asset/api/types.bal index c77bf755..9ee45085 100644 --- a/campus/bffs/asset/api/types.bal +++ b/campus/bffs/asset/api/types.bal @@ -1085,6 +1085,10 @@ public type GetInventoryDataByOrganizationResponse record {| int? id; int? avinya_type_id; int? consumable_id; + record {| + int? id; + string? name; + |}? consumable; anydata? quantity; anydata? quantity_in; anydata? quantity_out; diff --git a/campus/bffs/asset/graphql_client/asset.graphql b/campus/bffs/asset/graphql_client/asset.graphql index bfe030b9..70f21826 100644 --- a/campus/bffs/asset/graphql_client/asset.graphql +++ b/campus/bffs/asset/graphql_client/asset.graphql @@ -644,9 +644,13 @@ query getAvinyaTypes { query getInventoryDataByOrganization($organization_id: Int!,$date: String!) { inventory_data_by_organization(organization_id:$organization_id,date:$date) { - id + id avinya_type_id consumable_id + consumable{ + id + name + } quantity quantity_in quantity_out diff --git a/campus/bffs/asset/graphql_client/graphql_client_cg_src/client.bal b/campus/bffs/asset/graphql_client/graphql_client_cg_src/client.bal index 42bfd968..4130621e 100644 --- a/campus/bffs/asset/graphql_client/graphql_client_cg_src/client.bal +++ b/campus/bffs/asset/graphql_client/graphql_client_cg_src/client.bal @@ -232,7 +232,7 @@ public isolated client class GraphqlClient { return check performDataBinding(graphqlResponse, GetAvinyaTypesResponse); } remote isolated function getInventoryDataByOrganization(string date, int organization_id) returns GetInventoryDataByOrganizationResponse|graphql:ClientError { - string query = string `query getInventoryDataByOrganization($organization_id:Int!,$date:String!) {inventory_data_by_organization(organization_id:$organization_id,date:$date) {id avinya_type_id consumable_id quantity quantity_in quantity_out resource_property {id property value}}}`; + string query = string `query getInventoryDataByOrganization($organization_id:Int!,$date:String!) {inventory_data_by_organization(organization_id:$organization_id,date:$date) {id avinya_type_id consumable_id consumable {id name} quantity quantity_in quantity_out resource_property {id property value}}}`; map variables = {"date": date, "organization_id": organization_id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetInventoryDataByOrganizationResponse); diff --git a/campus/bffs/asset/graphql_client/graphql_client_cg_src/types.bal b/campus/bffs/asset/graphql_client/graphql_client_cg_src/types.bal index 4950c3e9..fe77ce8d 100644 --- a/campus/bffs/asset/graphql_client/graphql_client_cg_src/types.bal +++ b/campus/bffs/asset/graphql_client/graphql_client_cg_src/types.bal @@ -1091,6 +1091,10 @@ public type GetInventoryDataByOrganizationResponse record {| int? id; int? avinya_type_id; int? consumable_id; + record {| + int? id; + string? name; + |}? consumable; anydata? quantity; anydata? quantity_in; anydata? quantity_out; diff --git a/campus/frontend/lib/avinya/asset_admin/lib/app.dart b/campus/frontend/lib/avinya/asset_admin/lib/app.dart index d589e997..b37c822f 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/app.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/app.dart @@ -39,6 +39,7 @@ class _AssetAdminSystemState extends State { '/consumable_dashboard', '/stock_replenishment', '/consumable_monthly_report', + '/consumable_weekly_report', // '/assets/new', // '/assets/all', // '/assets/popular', @@ -123,6 +124,9 @@ class _AssetAdminSystemState extends State { final consumableMonthlyReportRoute = ParsedRoute('/consumable_monthly_report', '/consumable_monthly_report', {}, {}); + + final consumableWeeklyReportRoute = ParsedRoute( + '/consumable_weekly_report', '/consumable_weekly_report', {}, {}); // // Go to /apply if the user is not signed in log("_guard signed in $signedIn"); @@ -139,6 +143,8 @@ class _AssetAdminSystemState extends State { return stockReplenishmentRoute; } else if (signedIn && from == consumableMonthlyReportRoute){ return consumableMonthlyReportRoute; + } else if (signedIn && from == consumableWeeklyReportRoute) { + return consumableWeeklyReportRoute; } // Go to /application if the user is signed in and tries to go to /signin. else if (signedIn && from == signInRoute) { diff --git a/campus/frontend/lib/avinya/asset_admin/lib/data/stock_repenishment.dart b/campus/frontend/lib/avinya/asset_admin/lib/data/stock_repenishment.dart index 3266785d..333abb1c 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/data/stock_repenishment.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/data/stock_repenishment.dart @@ -148,3 +148,25 @@ Future> getConsumableMonthlyReport( throw Exception('Failed to get Consumable Monthly Summary Data'); } } + +Future> getConsumableWeeklyReport( + int organization_id, String from_date, String to_date) async { + final response = await http.get( + Uri.parse( + '${AppConfig.campusAssetsBffApiUrl}/consumable_weekly_report/$organization_id/$from_date/$to_date'), + 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 consumableWeeklySummaryData = await resultsJson + .map((json) => StockReplenishment.fromJson(json)) + .toList(); + return consumableWeeklySummaryData; + } else { + throw Exception('Failed to get Consumable Weekly Summary Data'); + } +} diff --git a/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_weekly_report.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_weekly_report.dart new file mode 100644 index 00000000..4c803372 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_weekly_report.dart @@ -0,0 +1,30 @@ + + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../widgets/consumable_weekly_report.dart'; + +class ConsumableWeeklyReportScreen extends StatefulWidget { + const ConsumableWeeklyReportScreen({super.key}); + + @override + State createState() => _ConsumableWeeklyReportScreenState(); +} + +class _ConsumableWeeklyReportScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text("Consumable Weekly Report"), + ), + body: SingleChildScrollView( + child: Container( + child: ConsumableWeeklyReport(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart index 1a3187ec..7ead5572 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart @@ -37,6 +37,10 @@ class _SMSScaffoldState extends State { ]; consumableReportDestinations = [ + SideNavigationSectionTile( + tileName: "Consumable Weekly Report", + route: "/consumable_weekly_report", + icon: Icons.summarize_outlined), SideNavigationSectionTile( tileName: "Consumable Monthly Report", route: "/consumable_monthly_report", diff --git a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold_body.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold_body.dart index f3441ad4..148d6a77 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold_body.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold_body.dart @@ -6,6 +6,7 @@ import 'package:gallery/avinya/asset_admin/lib/screens/asset_dashboard_screen.da import 'package:gallery/avinya/asset_admin/lib/screens/consumable_dashboard_screen.dart'; import 'package:gallery/avinya/asset_admin/lib/screens/stock_replenishment.dart'; import 'package:gallery/avinya/asset_admin/lib/screens/consumable_monthly_report.dart'; +import 'package:gallery/avinya/asset_admin/lib/screens/consumable_weekly_report.dart'; import 'package:gallery/avinya/asset_admin/lib/widgets/resource_allocation_report.dart'; import '../routing.dart'; @@ -55,6 +56,11 @@ class SMSScaffoldBody extends StatelessWidget { key: ValueKey('consumable_monthly_report'), child: ConsumableMonthlyReportScreen(), ) + else if (currentRoute.pathTemplate.startsWith('/consumable_weekly_report')) + const FadeTransitionPage( + key: ValueKey('consumable_weekly_report'), + child: ConsumableWeeklyReportScreen(), + ) // Avoid building a Navigator with an empty `pages` list when the // RouteState is set to an unexpected path, such as /signin. // diff --git a/campus/frontend/lib/avinya/asset_admin/lib/utils.dart b/campus/frontend/lib/avinya/asset_admin/lib/utils.dart new file mode 100644 index 00000000..c3f6e7d0 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/utils.dart @@ -0,0 +1,52 @@ +// Copyright 2019 Aleksander Woźniak +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:collection'; + +import 'package:table_calendar/table_calendar.dart'; + +/// Example event class. +class Event { + final String title; + + const Event(this.title); + + @override + String toString() => title; +} + +/// Example events. +/// +/// Using a [LinkedHashMap] is highly recommended if you decide to use a map. +final kEvents = LinkedHashMap>( + equals: isSameDay, + hashCode: getHashCode, +)..addAll(_kEventSource); + +final _kEventSource = Map.fromIterable(List.generate(50, (index) => index), + key: (item) => DateTime.utc(kFirstDay.year, kFirstDay.month, item * 5), + value: (item) => List.generate( + item % 4 + 1, (index) => Event('Event $item | ${index + 1}'))) + ..addAll({ + kToday: [ + Event('Today\'s Event 1'), + Event('Today\'s Event 2'), + ], + }); + +int getHashCode(DateTime key) { + return key.day * 1000000 + key.month * 10000 + key.year; +} + +/// Returns a list of [DateTime] objects from [first] to [last], inclusive. +List daysInRange(DateTime first, DateTime last) { + final dayCount = last.difference(first).inDays + 1; + return List.generate( + dayCount, + (index) => DateTime.utc(first.year, first.month, first.day + index), + ); +} + +final kToday = DateTime.now(); +final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day); +final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day); diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_monthly_report.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_monthly_report.dart index 7b9a4177..fe1b75cd 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_monthly_report.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_monthly_report.dart @@ -135,6 +135,7 @@ class _ConsumableMonthlyReportState extends State { ), if (_selected == null) Container( + margin: EdgeInsets.only(left: 10.0), child: const Text( 'No month & year selected.', style: TextStyle( @@ -157,7 +158,7 @@ class _ConsumableMonthlyReportState extends State { ), ), SizedBox( - height: 10, + height: 30, ), Wrap(children: [ if (_isFetching) @@ -170,7 +171,8 @@ class _ConsumableMonthlyReportState extends State { ), ) else if (_fetchedConsumableMonthlySummaryData.length > 0) - Center( + Container( + margin: EdgeInsets.only(left: 10.0,top: 20.0), child: ScrollConfiguration( behavior: ScrollConfiguration.of(context).copyWith(dragDevices: { @@ -210,14 +212,7 @@ class MyData extends DataTableSource { @override DataRow? getRow(int index) { - if (index == 0) { - List cells = List.filled( - 6, DataCell.empty); - return DataRow( - cells: cells, - ); - } if(_fetchedConsumableMonthlySummaryData.length > 0){ @@ -226,20 +221,20 @@ class MyData extends DataTableSource { cells[0] = DataCell(Text('')); cells[1] = DataCell( - Center(child: Text(_fetchedConsumableMonthlySummaryData[index - 1].consumable!.name.toString()))); + Center(child: Text(_fetchedConsumableMonthlySummaryData[index].consumable!.name.toString()))); cells[2] = DataCell( - Center(child: Text(_fetchedConsumableMonthlySummaryData[index - 1].quantity_in!.toStringAsFixed(2)))); + Center(child: Text(_fetchedConsumableMonthlySummaryData[index].quantity_in!.toStringAsFixed(2)))); cells[3] = DataCell( - Center(child: Text(_fetchedConsumableMonthlySummaryData[index - 1].quantity_out!.toStringAsFixed(2)))); + Center(child: Text(_fetchedConsumableMonthlySummaryData[index].quantity_out!.toStringAsFixed(2)))); - double? closingStock = (_fetchedConsumableMonthlySummaryData[index - 1].quantity_in!) - - (_fetchedConsumableMonthlySummaryData[index - 1].quantity_out!); + double? closingStock = (_fetchedConsumableMonthlySummaryData[index].quantity_in!) - + (_fetchedConsumableMonthlySummaryData[index].quantity_out!); cells[4] = DataCell(Center( child: Text(closingStock.toStringAsFixed(2)))); cells[5] = DataCell( - Center(child: Text(_fetchedConsumableMonthlySummaryData[index - 1].resource_property!.value.toString()))); + Center(child: Text(_fetchedConsumableMonthlySummaryData[index].resource_property!.value.toString()))); return DataRow(cells: cells); } diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_weekly_report.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_weekly_report.dart new file mode 100644 index 00000000..da8ba429 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_weekly_report.dart @@ -0,0 +1,324 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:gallery/avinya/asset_admin/lib/data/stock_repenishment.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:asset_admin/widgets/week_picker.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'dart:ui'; + +class ConsumableWeeklyReport extends StatefulWidget { + const ConsumableWeeklyReport({super.key}); + + @override + State createState() => _ConsumableWeeklyReportState(); +} + +class _ConsumableWeeklyReportState extends State { + List _fetchedConsumableWeeklySummaryData = []; + bool _isFetching = false; + DateTime? _selected; + + late DataTableSource _data; + + List dataTablecolumnNames = []; + var activityId = 0; + late int parentOrgId; + + late String formattedStartDate; + late String formattedEndDate; + var today = DateTime.now(); + DateTime? _selectedDay; + DateTime? _rangeStart; + DateTime? _rangeEnd; + + @override + void initState() { + super.initState(); + parentOrgId = campusAppsPortalInstance.getUserPerson().organization!.id!; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _data = MyData(_fetchedConsumableWeeklySummaryData, updateSelected); + // DateRangePicker(updateDateRange, formattedStartDate); + } + + + void updateSelected(int index, bool value, List selected) { + setState(() { + selected[index] = value; + }); + } + + void updateDateRange(_rangeStart, _rangeEnd) async { + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization!.id; + + if (parentOrgId != null) { + setState(() { + _isFetching = true; // Set _isFetching to true before starting the fetch + }); + try { + _fetchedConsumableWeeklySummaryData = + await getConsumableWeeklyReport( + parentOrgId, + DateFormat('yyyy-MM-dd').format(_rangeStart), + DateFormat('yyyy-MM-dd').format(_rangeEnd)); + + setState(() { + final startDate = _rangeStart ?? _selectedDay; + final endDate = _rangeEnd ?? _selectedDay; + final formatter = DateFormat('MMM d, yyyy'); + final formattedStartDate = formatter.format(startDate!); + final formattedEndDate = formatter.format(endDate!); + this.formattedStartDate = formattedStartDate; + this.formattedEndDate = formattedEndDate; + this._fetchedConsumableWeeklySummaryData = _fetchedConsumableWeeklySummaryData; + _isFetching = false; + }); + } catch (error) { + // Handle any errors that occur during the fetch + setState(() { + _isFetching = false; // Set _isFetching to false in case of error + }); + // Perform error handling, e.g., show an error message + } + } + } + + List _buildDataColumns() { + List columnNames = []; + + columnNames.add(DataColumn( + label: Text('Date', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Product Name', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Opening Stock(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Replenishment(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Balance(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Consumption(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Closing Stock(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + columnNames.add(DataColumn( + label: Text('Unit', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)))); + + return columnNames; + } + + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Wrap( + children: [ + Padding( + padding: EdgeInsets.only(top: 20, left: 20), + child: Row( + children: [ + Text( + 'Select a Week :', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + SizedBox( + width: 5, + ), + ElevatedButton( + style: ButtonStyle( + textStyle: MaterialStateProperty.all( + TextStyle(fontSize: 20), + ), + elevation: MaterialStateProperty.all(20), + backgroundColor: + MaterialStateProperty.all(Colors.greenAccent), + foregroundColor: + MaterialStateProperty.all(Colors.black), + ), + onPressed: _isFetching + ? null + : () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => WeekPicker( + updateDateRange, formattedStartDate)), + ), + child: Container( + height: 50, // Adjust the height as needed + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_isFetching) + Padding( + padding: EdgeInsets.only(right: 10), + child: SpinKitFadingCircle( + color: Colors + .black, // Customize the color of the indicator + size: + 20, // Customize the size of the indicator + ), + ), + if (!_isFetching) + Icon(Icons.calendar_today, color: Colors.black), + SizedBox(width: 10), + Text( + '${this.formattedStartDate} - ${this.formattedEndDate}', + style: TextStyle( + color: Colors.black, + fontSize: 17, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ], + ), + ), + SizedBox( + height: 30, + ), + Wrap(children: [ + if (_isFetching) + Container( + margin: EdgeInsets.only(top: 180), + child: SpinKitCircle( + color: (Colors + .yellow[700]), // Customize the color of the indicator + size: 50, // Customize the size of the indicator + ), + ) + else if (_fetchedConsumableWeeklySummaryData.length > 0) + Container( + margin: EdgeInsets.only(left: 10.0, top: 20.0), + child: 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: 50, + horizontalMargin: 60, + rowsPerPage: 10, + ), + ), + ) + else + Container( + margin: EdgeInsets.all(20), + child: Text('No consumable weekly summary data found'), + ), + ]), + ], + ), + ], + ), + ); + } +} + +class MyData extends DataTableSource { + MyData(this._fetchedConsumableWeeklySummaryData, this.updateSelected); + + final List _fetchedConsumableWeeklySummaryData; + final Function(int, bool, List) updateSelected; + + @override + DataRow? getRow(int index) { + + if (_fetchedConsumableWeeklySummaryData.isEmpty) { + return null; + } + + //Group the data by date + Map> groupedData = {}; + + for(var item in _fetchedConsumableWeeklySummaryData){ + final date = item.updated; + if(!groupedData.containsKey(date)){ + groupedData[DateTime.parse(date!)] = []; + } + groupedData[date]!.add(item); + } + + // Flatten the grouped data into a list with display flags + List<_GroupedItem> displayData = []; + groupedData.forEach((date,items) { + for(int i=0;i= displayData.length) { + return null; + } + + final groupedItem = displayData[index]; + final consumableItem = groupedItem.stockReplenishment; + + List cells = List.filled(8, DataCell.empty); + + // Display date only once for the first item of the group + cells[0] = DataCell(Text(groupedItem.showDate ? groupedItem.date.toString() : '')); + cells[1] = DataCell(Center(child: Text(groupedItem.showDate ? groupedItem.date.toString() : ''))); + cells[2] = DataCell(Center(child: Text(groupedItem.showDate ? groupedItem.date.toString() : ''))); + cells[3] = DataCell(Center(child: Text(groupedItem.showDate ? groupedItem.date.toString() : ''))); + cells[4] = DataCell(Text(groupedItem.showDate ? groupedItem.date.toString() : '')); + cells[5] = DataCell(Text(groupedItem.showDate ? groupedItem.date.toString() : '')); + cells[6] = DataCell(Text(groupedItem.showDate ? groupedItem.date.toString() : '')); + cells[7] = DataCell(Text(groupedItem.showDate ? groupedItem.date.toString() : '')); + + return null; + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount { + int count = 0; + if (_fetchedConsumableWeeklySummaryData.length > 0) { + count = _fetchedConsumableWeeklySummaryData.length; + } + return count; + } + + @override + int get selectedRowCount => 0; +} + +class _GroupedItem { + final DateTime date; + final StockReplenishment stockReplenishment; + final bool showDate; + + _GroupedItem(this.date, this.stockReplenishment, this.showDate); +} diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/week_picker.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/week_picker.dart new file mode 100644 index 00000000..dbeda037 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/week_picker.dart @@ -0,0 +1,135 @@ +// Copyright 2019 Aleksander Woźniak +// SPDX-License-Identifier: Apache-2.0 + +import 'package:flutter/material.dart'; +import 'package:attendance/widgets/weekly_payment_report.dart'; +import 'package:intl/intl.dart'; +import 'package:table_calendar/table_calendar.dart'; + +import '../utils.dart'; + +class WeekPicker extends StatefulWidget { + WeekPicker(this.updateDateRange, this.formattedStartDate); + final Function(DateTime, DateTime) updateDateRange; + final String formattedStartDate; + + @override + _WeekPickerState createState() => _WeekPickerState(); +} + +class _WeekPickerState extends State { + CalendarFormat _calendarFormat = CalendarFormat.month; + + RangeSelectionMode _rangeSelectionMode = RangeSelectionMode + .toggledOn; // Can be toggled on/off by longpressing a date + DateTime _focusedDay = DateTime.now(); + DateTime? _selectedDay; + DateTime? _rangeStart; + DateTime? _rangeEnd; + + void selectWeek(DateTime selectedDay) { + // Calculate the start of the week (excluding weekends) based on the selected day + DateTime startOfWeek = + selectedDay.subtract(Duration(days: selectedDay.weekday - 1)); + while (startOfWeek.weekday > DateTime.friday) { + startOfWeek = startOfWeek.subtract(Duration(days: 1)); + } + + // Calculate the end of the week (excluding weekends) based on the start of the week + DateTime endOfWeek = startOfWeek.add(Duration(days: 4)); + + // Update the variables to select the week + _focusedDay = startOfWeek; + _selectedDay = selectedDay; + _rangeStart = startOfWeek; + _rangeEnd = endOfWeek; + } + + @override + void didChangeDependencies() { + if (_selectedDay != null) { + selectWeek(_selectedDay!); + } else { + String dateString = widget.formattedStartDate; + DateTime dateTime = DateFormat('MMM d, yyyy').parse(dateString); + + _selectedDay = dateTime; + selectWeek(_selectedDay!); + } + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (_rangeStart != null && _rangeEnd != null) { + widget.updateDateRange(_rangeStart!, _rangeEnd!); + } + return true; // Return true to allow the back navigation + }, + child: Scaffold( + appBar: AppBar( + title: Text('TableCalendar - Range',style: TextStyle(color: Colors.black)), + backgroundColor: Color.fromARGB(255, 236, 230, 253), + iconTheme: IconThemeData(color: Colors.black), + ), + body: TableCalendar( + firstDay: kFirstDay, + lastDay: kLastDay, + focusedDay: _focusedDay, + selectedDayPredicate: (day) => isSameDay(_selectedDay, day), + rangeStartDay: _rangeStart, + rangeEndDay: _rangeEnd, + calendarFormat: _calendarFormat, + rangeSelectionMode: _rangeSelectionMode, + onDaySelected: (selectedDay, focusedDay) { + if (!isSameDay(_selectedDay, selectedDay)) { + setState(() { + _selectedDay = selectedDay; + _focusedDay = focusedDay; + _rangeStart = null; // Important to clean those + _rangeEnd = null; + _rangeSelectionMode = RangeSelectionMode.toggledOff; + + // Update the variables to select the week (excluding weekends) + selectWeek(selectedDay); + }); + } + }, + onRangeSelected: (start, end, focusedDay) { + setState(() { + _selectedDay = null; + _focusedDay = focusedDay; + _rangeStart = start; + _rangeEnd = end; + _rangeSelectionMode = RangeSelectionMode.toggledOn; + + // Update the variables to select the week (excluding weekends) + selectWeek(start!); + }); + }, + onFormatChanged: (format) { + if (_calendarFormat != format) { + setState(() { + _calendarFormat = format; + }); + } + }, + onPageChanged: (focusedDay) { + _focusedDay = focusedDay; + }, + ), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.deepPurpleAccent, + onPressed: () async { + if (_rangeStart != null && _rangeEnd != null) { + widget.updateDateRange(_rangeStart!, _rangeEnd!); + } + Navigator.of(context).pop(); + }, + child: const Icon(Icons.save), + )), + ); + } +}