From 7b517163d2d3990aef5dbfa25787453540c0ece3 Mon Sep 17 00:00:00 2001 From: lahirulakruwan Date: Mon, 5 Aug 2024 18:08:34 +0530 Subject: [PATCH 1/3] consumable dashboard changes added --- campus/bffs/asset/api/Dependencies.toml | 2 +- campus/bffs/asset/api/client.bal | 7 + campus/bffs/asset/api/service.bal | 24 + campus/bffs/asset/api/types.bal | 19 + .../bffs/asset/graphql_client/asset.graphql | 17 + .../graphql_client_cg_src/client.bal | 6 + .../graphql_client_cg_src/types.bal | 21 +- .../bffs/asset/graphql_client/schema.graphql | 5 +- campus/bffs/asset/graphql_client/schema.json | 81 ++- .../lib/avinya/asset_admin/lib/app.dart | 4 +- .../lib/data/stock_repenishment.dart | 22 + .../screens/consumable_dashboard_screen.dart | 4 +- .../asset_admin/lib/screens/scaffold.dart | 47 ++ .../lib/widgets/consumable_bar_chart.dart | 460 ++++++++++++++++++ .../lib/widgets/consumable_dashboard.dart | 37 ++ .../lib/widgets/latest_consumable_data.dart | 241 +++++++++ .../lib/avinya/asset_admin/pubspec.yaml | 1 + campus/frontend/lib/pages/home.dart | 2 +- 18 files changed, 984 insertions(+), 16 deletions(-) create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart diff --git a/campus/bffs/asset/api/Dependencies.toml b/campus/bffs/asset/api/Dependencies.toml index ba28474a..ec2c1d61 100644 --- a/campus/bffs/asset/api/Dependencies.toml +++ b/campus/bffs/asset/api/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.5.0" [[package]] org = "avinyafoundation" name = "asset_bff" -version = "0.1.0" +version = "1.1.0" dependencies = [ {org = "ballerina", name = "graphql"}, {org = "ballerina", name = "http"}, diff --git a/campus/bffs/asset/api/client.bal b/campus/bffs/asset/api/client.bal index db9d0859..9593653d 100644 --- a/campus/bffs/asset/api/client.bal +++ b/campus/bffs/asset/api/client.bal @@ -372,4 +372,11 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetConsumableMonthlyReportResponse); } + + remote isolated function getConsumableYearlyReport(int consumable_id, int year, int organization_id) returns GetConsumableYearlyReportResponse|graphql:ClientError { + string query = string `query getConsumableYearlyReport($organization_id:Int!,$consumable_id:Int!,$year:Int!) {consumable_yearly_report(organization_id:$organization_id,consumable_id:$consumable_id,year:$year) {consumable {id name} month_name quantity_in quantity_out resource_property {id property value}}}`; + map variables = {"consumable_id": consumable_id, "year": year, "organization_id": organization_id}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, GetConsumableYearlyReportResponse); + } } diff --git a/campus/bffs/asset/api/service.bal b/campus/bffs/asset/api/service.bal index 7a4372f0..c5346c7a 100644 --- a/campus/bffs/asset/api/service.bal +++ b/campus/bffs/asset/api/service.bal @@ -855,4 +855,28 @@ service / on new http:Listener(9094) { ":: Detail: " + getConsumableMonthlyReportResponse.detail().toString()); } } + + resource function get consumable_yearly_report/[int organization_id]/[int consumable_id]/[int year]() returns Inventory[]|error { + GetConsumableYearlyReportResponse|graphql:ClientError getConsumableYearlyReportResponse = globalDataClient->getConsumableYearlyReport(consumable_id,year,organization_id); + if (getConsumableYearlyReportResponse is GetConsumableYearlyReportResponse) { + Inventory[] yearly_summary_consumable_datas = []; + foreach var yearly_summary_consumable_data in getConsumableYearlyReportResponse.consumable_yearly_report { + Inventory|error yearly_summary_consumable_data_record = yearly_summary_consumable_data.cloneWithType(Inventory); + if (yearly_summary_consumable_data_record is Inventory) { + yearly_summary_consumable_datas.push(yearly_summary_consumable_data_record); + } else { + log:printError("Error while processing Application record received", yearly_summary_consumable_data_record); + return error("Error while processing Application record received: " + yearly_summary_consumable_data_record.message() + + ":: Detail: " + yearly_summary_consumable_data_record.detail().toString()); + } + } + + return yearly_summary_consumable_datas; + + } else { + log:printError("Error while getting application", getConsumableYearlyReportResponse); + return error("Error while getting application: " + getConsumableYearlyReportResponse.message() + + ":: Detail: " + getConsumableYearlyReportResponse.detail().toString()); + } + } } diff --git a/campus/bffs/asset/api/types.bal b/campus/bffs/asset/api/types.bal index 911a11eb..568085c8 100644 --- a/campus/bffs/asset/api/types.bal +++ b/campus/bffs/asset/api/types.bal @@ -234,6 +234,7 @@ public type EvaluationMetadata record { // int? person_id?; // }; public type Inventory record { + string? month_name?; int? consumable_id?; anydata? quantity?; string? created?; @@ -1149,4 +1150,22 @@ public type GetConsumableMonthlyReportResponse record {| string? value; |}? resource_property; |}[] consumable_monthly_report; +|}; + +public type GetConsumableYearlyReportResponse record {| + map __extensions?; + record {| + record {| + int? id; + string? name; + |}? consumable; + string? month_name; + anydata? quantity_in; + anydata? quantity_out; + record {| + int? id; + string? property; + string? value; + |}? resource_property; + |}[] consumable_yearly_report; |}; \ No newline at end of file diff --git a/campus/bffs/asset/graphql_client/asset.graphql b/campus/bffs/asset/graphql_client/asset.graphql index 0b09ec86..c243b52a 100644 --- a/campus/bffs/asset/graphql_client/asset.graphql +++ b/campus/bffs/asset/graphql_client/asset.graphql @@ -708,4 +708,21 @@ query getConsumableMonthlyReport($organization_id: Int!,$year: Int!,$month: Int! value } } +} + +query getConsumableYearlyReport($organization_id: Int!,$consumable_id: Int!,$year: Int!) { + consumable_yearly_report(organization_id: $organization_id,consumable_id: $consumable_id,year: $year) { + consumable{ + id + name + } + month_name + quantity_in + quantity_out + resource_property{ + id + property + value + } + } } \ No newline at end of file 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 5c5b63f3..2611265e 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 @@ -249,4 +249,10 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetConsumableMonthlyReportResponse); } + remote isolated function getConsumableYearlyReport(int consumable_id, int year, int organization_id) returns GetConsumableYearlyReportResponse|graphql:ClientError { + string query = string `query getConsumableYearlyReport($organization_id:Int!,$consumable_id:Int!,$year:Int!) {consumable_yearly_report(organization_id:$organization_id,consumable_id:$consumable_id,year:$year) {consumable {id name} month_name quantity_in quantity_out resource_property {id property value}}}`; + map variables = {"consumable_id": consumable_id, "year": year, "organization_id": organization_id}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, GetConsumableYearlyReportResponse); + } } 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 2ddd2705..3810d55b 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 @@ -238,6 +238,7 @@ public type EvaluationMetadata record { }; public type Inventory record { + string? month_name?; int? consumable_id?; anydata? quantity?; string? created?; @@ -557,7 +558,7 @@ public type GetConsumablesResponse record {| string? manufacturer; string? model; string? serial_number; - |}[] consumables; + |}[]? consumables; |}; public type AddConsumableResponse record {| @@ -1156,3 +1157,21 @@ public type GetConsumableMonthlyReportResponse record {| |}? resource_property; |}[]? consumable_monthly_report; |}; + +public type GetConsumableYearlyReportResponse record {| + map __extensions?; + record {| + record {| + int? id; + string? name; + |}? consumable; + string? month_name; + anydata? quantity_in; + anydata? quantity_out; + record {| + int? id; + string? property; + string? value; + |}? resource_property; + |}[]? consumable_yearly_report; +|}; diff --git a/campus/bffs/asset/graphql_client/schema.graphql b/campus/bffs/asset/graphql_client/schema.graphql index cd95500d..bb27c3a0 100644 --- a/campus/bffs/asset/graphql_client/schema.graphql +++ b/campus/bffs/asset/graphql_client/schema.graphql @@ -547,6 +547,7 @@ input Inventory { asset_id: Int consumable_id: Int name: String + month_name: String description: String manufacturer: String organization_id: Int @@ -576,6 +577,7 @@ type InventoryData { prev_quantity: Decimal resource_property: ResourcePropertyData name: String + month_name: String description: String manufacturer: String created: String @@ -872,7 +874,7 @@ type Query { suppliers: [SupplierData!]! consumable(id: Int!): ConsumableData consumableByUpdate(updated: String, avinya_type_id: Int): [ConsumableData!] - consumables: [ConsumableData!]! + consumables: [ConsumableData!] activeActivityInstance: [ActivityInstanceData!]! resource_property(id: Int!): ResourcePropertyData resource_properties: [ResourcePropertyData!]! @@ -899,6 +901,7 @@ type Query { inventory_data_by_organization(organization_id: Int, date: String = ""): [InventoryData!] consumable_weekly_report(organization_id: Int, from_date: String = "", to_date: String = ""): [InventoryData!] consumable_monthly_report(organization_id: Int, year: Int, month: Int): [InventoryData!] + consumable_yearly_report(organization_id: Int, consumable_id: Int, year: Int): [InventoryData!] } input ResourceAllocation { diff --git a/campus/bffs/asset/graphql_client/schema.json b/campus/bffs/asset/graphql_client/schema.json index 78f3d623..220c6cf2 100644 --- a/campus/bffs/asset/graphql_client/schema.json +++ b/campus/bffs/asset/graphql_client/schema.json @@ -1993,19 +1993,15 @@ "name": "consumables", "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "ConsumableData", - "ofType": null - } + "kind": "OBJECT", + "name": "ConsumableData", + "ofType": null } } }, @@ -2911,6 +2907,53 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "consumable_yearly_report", + "args": [ + { + "name": "organization_id", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "consumable_id", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "year", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "InventoryData", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -6189,6 +6232,17 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "month_name", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "description", "args": [], @@ -8970,6 +9024,15 @@ }, "defaultValue": null }, + { + "name": "month_name", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, { "name": "description", "type": { diff --git a/campus/frontend/lib/avinya/asset_admin/lib/app.dart b/campus/frontend/lib/avinya/asset_admin/lib/app.dart index b98fe800..76756fd7 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/app.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/app.dart @@ -51,7 +51,7 @@ class _AssetAdminSystemState extends State { ], guard: _guard, // initialRoute: '/signin', - initialRoute: '/asset_dashboard', + initialRoute: '/consumable_dashboard', ); _routeState = RouteState(_routeParser); @@ -172,7 +172,7 @@ class _AssetAdminSystemState extends State { bool signedIn = await _auth.getSignedIn(); if (!signedIn) { // _routeState.go('/signin'); - _routeState.go('/asset_dashboard'); + _routeState.go('/consumable_dashboard'); } } 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 a4866201..218fd3ac 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 @@ -235,3 +235,25 @@ Future> getConsumableWeeklyReport( throw Exception('Failed to get Consumable Weekly Summary Data'); } } + +Future> getConsumableYearlyReport( + int organization_id, int consumable_id, int year) async { + final response = await http.get( + Uri.parse( + '${AppConfig.campusAssetsBffApiUrl}/consumable_yearly_report/$organization_id/$consumable_id/$year'), + 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 consumableYearlySummaryData = await resultsJson + .map((json) => StockReplenishment.fromJson(json)) + .toList(); + return consumableYearlySummaryData; + } else { + throw Exception('Failed to get Consumable Yearly Summary Data'); + } +} diff --git a/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_dashboard_screen.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_dashboard_screen.dart index 0dddc88d..f6e19ada 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_dashboard_screen.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/consumable_dashboard_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../widgets/consumable_dashboard.dart'; + class ConsumableDashboardScreen extends StatefulWidget { const ConsumableDashboardScreen({super.key}); @@ -11,7 +13,7 @@ class _ConsumableDashboardScreenState extends State { @override Widget build(BuildContext context) { return const Center( - child: Text('Consumable Dashboard Screen'), + child: ConsumableDashboard(), ); } } \ 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 2c89e2e2..0f562fa9 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart @@ -15,6 +15,7 @@ class SMSScaffold extends StatefulWidget { class _SMSScaffoldState extends State { bool isAssetDashboardSectionHovered = false; + bool isConsumableDashboardSectionHovered = false; bool isAssetSectionHovered = false; bool isAssetReportSectionHovered = false; bool isConsumableSectionHovered = false; @@ -207,6 +208,52 @@ class _SMSScaffoldState extends State { //), ), ), + MouseRegion( + onEnter: (_) { + setState(() { + isConsumableDashboardSectionHovered = true; + }); + }, + onExit: (_) { + setState(() { + isConsumableDashboardSectionHovered = false; + }); + }, + child: Container( + decoration: BoxDecoration( + color: isConsumableDashboardSectionHovered + ? Colors.white.withOpacity(0.3) + : null, + borderRadius: BorderRadius.circular(15.0), + ), + margin: EdgeInsets.all(8.0), + child: Material( + type: MaterialType.transparency, + child: Container( + child: ListTile( + leading: Icon(Icons.space_dashboard_rounded, + color: Colors.white, size: 20.0), + title: Container( + margin: EdgeInsets.only(left: 12.0), + transform: Matrix4.translationValues(-25, 0.0, 0.0), + child: Text( + "Consumable Dashboard", + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + onTap: () { + Navigator.pop(context); // Close the drawer + routeState.go('/consumable_dashboard'); + }, + ), + ), + ), + // ), + ), + ), MouseRegion( onEnter: (_) { setState(() { diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart new file mode 100644 index 00000000..89823c93 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart @@ -0,0 +1,460 @@ +import 'dart:ui'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:gallery/avinya/asset_admin/lib/data/consumable.dart'; +import 'package:gallery/avinya/asset_admin/lib/data/stock_repenishment.dart'; +import 'package:gallery/constants.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:intl/intl.dart'; + +class ConsumableBarChart extends StatefulWidget { + const ConsumableBarChart({super.key}); + + @override + State createState() => _ConsumableBarChartState(); +} + +class _ConsumableBarChartState extends State { + Consumable? _selectedConsumableValue; + late Future> _fetchConsumableData; + DateTime _selectedYear = DateTime.now(); + bool _isFetching = false; + String? _selectedConsumableUnitValue; + + List _fetchedConsumableYearlySummaryData = []; + List _consumableItemQuantityInForYear = []; + List _consumableItemQuantityOutForYear = []; + + @override + void initState() { + super.initState(); + _fetchConsumableData = _loadConsumableData(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + Future> _loadConsumableData() async { + return await fetchConsumables(); + } + + void _showPicker(BuildContext context) { + showDialog( + context: context, + builder: ((context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0))), + title: Row( + children: [ + Icon(Icons.calendar_today), + SizedBox(width: 8), + Text('Select a Year'), + ], + ), + content: Container( + width: 300, + height: 300, + child: YearPicker( + firstDate: DateTime(DateTime.now().year - 100), + lastDate: DateTime(DateTime.now().year + 120), + initialDate: DateTime.now(), + selectedDate: _selectedYear, + onChanged: (DateTime dateTime) async { + Navigator.pop(context); + setState(() { + _selectedYear = dateTime; + _isFetching = true; + }); + + if (_selectedYear != null && + _selectedConsumableValue != null) { + int? parentOrgId = campusAppsPortalInstance + .getUserPerson() + .organization! + .id; + + _fetchedConsumableYearlySummaryData = + await getConsumableYearlyReport(parentOrgId!, + _selectedConsumableValue!.id!, _selectedYear.year); + + _selectedConsumableUnitValue = + _fetchedConsumableYearlySummaryData + .first.resource_property!.value; + + _consumableItemQuantityInForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_in!) + .toList(); + _consumableItemQuantityOutForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_out!) + .toList(); + + setState(() { + _fetchedConsumableYearlySummaryData; + _consumableItemQuantityInForYear; + _consumableItemQuantityOutForYear; + _isFetching = false; + }); + } + }, + ), + ), + ); + })); + } + + List _getBarGroups() { + return List.generate(12, (i) { + return BarChartGroupData( + barsSpace: 5, + x: i, + barRods: [ + BarChartRodData( + borderSide: BorderSide(width: 2.0), + borderRadius: BorderRadius.zero, + width: 20, + toY: _getQuantityInValue(i), + color: Colors.green, + backDrawRodData: BackgroundBarChartRodData( + show: true, + color: Colors.green.withOpacity(0.2), + ), + rodStackItems: [ + BarChartRodStackItem(0, _getQuantityInValue(i), Colors.green), + ], + ), + BarChartRodData( + borderSide: BorderSide(width: 2.0), + borderRadius: BorderRadius.zero, + width: 20, + toY: _getQuantityOutValue(i), + color: Colors.red, + backDrawRodData: BackgroundBarChartRodData( + show: true, + color: Colors.red.withOpacity(0.2), + ), + rodStackItems: [ + BarChartRodStackItem(0, _getQuantityOutValue(i), Colors.red), + ], + ), + ], + ); + }); + } + + double _getQuantityInValue(int month) { + return _consumableItemQuantityInForYear[month]; + } + + double _getQuantityOutValue(int month) { + return _consumableItemQuantityOutForYear[month]; + } + + Widget getTitles(double value, TitleMeta meta) { + const style = TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 14, + ); + Widget text; + switch (value.toInt()) { + case 0: + text = Center(child: const Text('Jan', style: style)); + break; + case 1: + text = Center(child: const Text('Feb', style: style)); + break; + case 2: + text = Center(child: const Text('Mar', style: style)); + break; + case 3: + text = Center(child: const Text('Apr', style: style)); + break; + case 4: + text = Center(child: const Text('May', style: style)); + break; + case 5: + text = Center(child: const Text('Jun', style: style)); + break; + case 6: + text = Center(child: const Text('Jul', style: style)); + break; + case 7: + text = Center(child: const Text('Aug', style: style)); + break; + case 8: + text = Center(child: const Text('Sep', style: style)); + break; + case 9: + text = Center(child: const Text('Oct', style: style)); + break; + case 10: + text = Center(child: const Text('Nov', style: style)); + break; + default: + text = Center(child: const Text('Dec', style: style)); + break; + } + return SideTitleWidget( + axisSide: meta.axisSide, + space: 0, + child: text, + ); + } + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 16 / 9, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Center( + child: Text( + 'Consumable Yearly Report', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.purple, // Matches yellow[700] + ), + ), + ), + Flexible( + flex: 2, + child: Row( + children: [ + Text( + 'Select a Consumable :', + style: TextStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold), + ), + FutureBuilder>( + future: _fetchConsumableData, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Container( + margin: EdgeInsets.only(top: 10), + child: SpinKitCircle( + color: (Colors.yellow[700]), + size: 70, + ), + ); + } else if (snapshot.hasError) { + return const Center( + child: Text('Something went wrong...'), + ); + } else if (!snapshot.hasData) { + return const Center( + child: Text('No consumable data found'), + ); + } + final consumableData = snapshot.data!; + return DropdownButton( + value: _selectedConsumableValue, + items: consumableData.map((Consumable consumable) { + return DropdownMenuItem( + value: consumable, + child: Text(consumable.name! ?? '')); + }).toList(), + onChanged: (Consumable? newValue) async { + if (newValue == null) { + return; + } + + int consumableId = newValue.id!; + + int? parentOrgId = campusAppsPortalInstance + .getUserPerson() + .organization! + .id; + + _fetchedConsumableYearlySummaryData = + await getConsumableYearlyReport(parentOrgId!, + consumableId, _selectedYear.year); + + _selectedConsumableUnitValue = + _fetchedConsumableYearlySummaryData + .first.resource_property!.value; + + _consumableItemQuantityInForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_in!) + .toList(); + _consumableItemQuantityOutForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_out!) + .toList(); + + setState(() { + _fetchedConsumableYearlySummaryData; + _selectedConsumableValue = newValue; + _consumableItemQuantityInForYear; + _consumableItemQuantityOutForYear; + }); + }); + }), + const SizedBox( + width: 20.0, + ), + Text( + 'Select a Year:', + style: TextStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.bold), + ), + SizedBox( + width: 5, + ), + TextButton( + onPressed: () => _showPicker(context), + child: Icon(Icons.calendar_month), + ), + if (_selectedYear == null) + Container( + margin: EdgeInsets.only(left: 10.0), + child: const Text( + 'No year selected.', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ) + else + Container( + child: Text( + _selectedYear.year.toString(), + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.normal), + ), + ), + SizedBox( + width: 20.0, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 70.0, + height: 30.0, + child: Text( + 'Quantity In', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: 5, + ), + SizedBox( + child: Container( + width: 20, + height: 20, + color: Colors.green, + ), + ), + ], + ), + SizedBox( + width: 5, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80.0, + height: 30.0, + child: Text( + 'Quantity Out', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: 5, + ), + SizedBox( + child: Container( + width: 20, + height: 20, + color: Colors.red, + ), + ), + ], + ), + ], + ), + ), + const SizedBox( + height: 5.0, + ), + Center( + child: Text( + 'Unit - ${_selectedConsumableUnitValue ?? ''}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox( + height: 20.0, + ), + if (_consumableItemQuantityInForYear.length > 0 && + _consumableItemQuantityOutForYear.length > 0) + Flexible( + flex: 3, + child: BarChart( + BarChartData( + groupsSpace: 30.0, + alignment: BarChartAlignment.spaceAround, + barGroups: _getBarGroups(), + titlesData: FlTitlesData( + show: true, + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false)), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + getTitlesWidget: getTitles, + showTitles: true, + reservedSize: 50))), + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: false), + ), + ), + ) + else + Container( + margin: EdgeInsets.only(left: 10.0), + child: const Text( + 'No data', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ) + ], + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart new file mode 100644 index 00000000..e964804f --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'consumable_bar_chart.dart'; +import 'latest_consumable_data.dart'; + +class ConsumableDashboard extends StatefulWidget { + const ConsumableDashboard({super.key}); + + @override + State createState() => _ConsumableDashboardState(); +} + +class _ConsumableDashboardState extends State { + @override + Widget build(BuildContext context) { + return Row( + children: [ + Flexible( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(left: 20, top: 25.0), + child: LatestConsumableData(), + ), + ), + Flexible( + flex: 3, + child: Padding( + padding: const EdgeInsets.only(left: 20, top: 25.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConsumableBarChart()), + )) + ], + ); + } +} diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart new file mode 100644 index 00000000..4d571728 --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart @@ -0,0 +1,241 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:gallery/avinya/asset_admin/lib/data/stock_repenishment.dart'; +import 'package:gallery/data/campus_apps_portal.dart'; +import 'package:intl/intl.dart'; + +class LatestConsumableData extends StatefulWidget { + const LatestConsumableData({super.key}); + + @override + State createState() => _LatestConsumableDataState(); +} + +class _LatestConsumableDataState extends State { + + List _fetchedLatestConsumableData = []; + bool _isFetching = false; + DateTime? _selected; + + late DataTableSource _data; + + List dataTablecolumnNames = []; + late int parentOrgId; + + @override + void initState() { + super.initState(); + _fetchInitialData(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _data = MyData(_fetchedLatestConsumableData,isQuantityBelowThreshold, + getThresholdValue); + } + + Future _fetchInitialData() async { + int? parentOrgId = + campusAppsPortalInstance.getUserPerson().organization?.id; + if (parentOrgId != null) { + setState(() { + _isFetching = true; // Show loading indicator + }); + try { + _fetchedLatestConsumableData = await getStockListforReplenishment(parentOrgId, + DateFormat('yyyy-MM-dd').format(DateTime.now())); + + setState(() { + _isFetching = false; + _data = MyData(_fetchedLatestConsumableData, isQuantityBelowThreshold, + getThresholdValue); + }); + } catch (error) { + // Handle any errors that occur during the fetch + // You can show an error message or take appropriate actions here + } finally { + setState(() { + _isFetching = false; // Hide loading indicator + }); + } + } + } + + bool isQuantityBelowThreshold(String unit,double quantity){ + + const Map thresholds = { + 'kg': 2.0, + 'g': 500.0, + 'packet': 2.0, + 'cup': 10.0, + 'bags': 10.0, + 'rolls': 1.0, + 'litre': 2.0, + 'ml': 100.0, + 'piece': 20.0, + }; + + if(thresholds.containsKey(unit)){ + return quantity < thresholds[unit]!; + } + return false; + } + + double getThresholdValue(String unit){ + + const Map thresholds = { + 'kg': 2.0, + 'g': 500.0, + 'packet': 2.0, + 'cup': 10.0, + 'bags': 10.0, + 'rolls': 1.0, + 'litre': 2.0, + 'ml': 100.0, + 'piece': 20.0, + }; + + return thresholds.containsKey(unit) ? thresholds[unit]! : 0.0; + } + + List _buildDataColumns() { + List columnNames = []; + + columnNames.add(DataColumn( + label: Center( + child: Text('Product Name', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); + + columnNames.add(DataColumn( + label: Center( + child: Text('On Hand(QTY)', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); + + columnNames.add(DataColumn( + label: Center( + child: Text('Unit', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); + + columnNames.add(DataColumn( + label: Center( + child: Text('Threshold', + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); + + return columnNames; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Wrap(children: [ + Center( + child: Text( + 'Latest Consumable Data', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.purple, + ), + ), + ), + if (_isFetching) + Container( + margin: EdgeInsets.only(top: 180), + child: SpinKitCircle( + color: (Colors + .yellow[700]), + size: 50, + ), + ) + else if (_fetchedLatestConsumableData.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(), + columnSpacing:35, + horizontalMargin: 30, + rowsPerPage: 10, + ), + ), + ) + else + Container( + margin: EdgeInsets.all(20), + child: Text('No latest consumable data found'), + ), + ]), + ); + } +} + +class MyData extends DataTableSource { + MyData(this._fetchedLatestConsumableData,this.isQuantityBelowThreshold,this.getThresholdValue); + + final List _fetchedLatestConsumableData; + final Function(String,double) isQuantityBelowThreshold; + final Function(String) getThresholdValue; + + @override + DataRow? getRow(int index) { + + var consumableItem = _fetchedLatestConsumableData[index]; + + bool isBelowThreshold = isQuantityBelowThreshold( + consumableItem.resource_property!.value.toString(), + consumableItem.quantity! + ); + + double threshold = getThresholdValue(consumableItem.resource_property!.value!); + + List cells = List.filled(4, DataCell.empty); + + cells[0] = DataCell(Center( + child: Text(consumableItem.consumable!.name.toString()))); + cells[1] = DataCell( + Center(child: Text(consumableItem.quantity.toString()))); + cells[2] = + DataCell( + Center(child: Text(consumableItem.resource_property!.value.toString()))); + cells[3] = + DataCell( + Center(child: Text(threshold.toString()))); + + + return DataRow( + color: MaterialStateProperty.resolveWith((Set states){ + return isBelowThreshold ?Colors.red[300]:null; + }), + cells: cells + ); + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount { + int count = 0; + if (_fetchedLatestConsumableData.length > 0) { + count = _fetchedLatestConsumableData.length; + } + return count; + } + + @override + int get selectedRowCount => 0; +} diff --git a/campus/frontend/lib/avinya/asset_admin/pubspec.yaml b/campus/frontend/lib/avinya/asset_admin/pubspec.yaml index 7ed9fa95..8366eb0a 100644 --- a/campus/frontend/lib/avinya/asset_admin/pubspec.yaml +++ b/campus/frontend/lib/avinya/asset_admin/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: path: plugins/window_size month_year_picker: ^0.3.0+1 oktoast: ^3.4.0 + fl_chart: ^0.60.0 dev_dependencies: flutter_lints: ^2.0.1 diff --git a/campus/frontend/lib/pages/home.dart b/campus/frontend/lib/pages/home.dart index 70fa47c9..40263fee 100644 --- a/campus/frontend/lib/pages/home.dart +++ b/campus/frontend/lib/pages/home.dart @@ -156,7 +156,7 @@ class HomePage extends StatelessWidget { studyRoute: asset_admin_routes.assetadminRoute, ), ), - // //2023-04-19 commented for prod and stag branches + //2023-04-19 commented for prod and stag branches // Padding( // padding: const EdgeInsets.all(10.0), // child: _CarouselCard( From d7f398cb81c8fb9bddbe07a94963283c6cb9a446 Mon Sep 17 00:00:00 2001 From: lahirulakruwan Date: Thu, 8 Aug 2024 18:51:06 +0530 Subject: [PATCH 2/3] Vehicle fuel consumption frontend changes added --- .../lib/avinya/asset_admin/lib/app.dart | 7 + .../asset_admin/lib/screens/scaffold.dart | 42 +++ .../lib/screens/scaffold_body.dart | 7 + .../lib/screens/vehicle_fuel_consumption.dart | 26 ++ .../lib/widgets/vehicle_fuel_consumption.dart | 333 ++++++++++++++++++ 5 files changed, 415 insertions(+) create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption.dart create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption.dart diff --git a/campus/frontend/lib/avinya/asset_admin/lib/app.dart b/campus/frontend/lib/avinya/asset_admin/lib/app.dart index 76756fd7..bbfa96c1 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/app.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/app.dart @@ -41,6 +41,7 @@ class _AssetAdminSystemState extends State { '/stock_depletion', '/consumable_monthly_report', '/consumable_weekly_report', + '/vehicle_fuel_consumption', // '/assets/new', // '/assets/all', // '/assets/popular', @@ -136,6 +137,10 @@ class _AssetAdminSystemState extends State { final consumableWeeklyReportRoute = ParsedRoute( '/consumable_weekly_report', '/consumable_weekly_report', {}, {}); + + final vehicleFuelConsumptionRoute = ParsedRoute( + '/vehicle_fuel_consumption', '/vehicle_fuel_consumption', {}, {}); + // // Go to /apply if the user is not signed in log("_guard signed in $signedIn"); @@ -156,6 +161,8 @@ class _AssetAdminSystemState extends State { return consumableMonthlyReportRoute; } else if (signedIn && from == consumableWeeklyReportRoute) { return consumableWeeklyReportRoute; + } else if (signedIn && from == vehicleFuelConsumptionRoute) { + return vehicleFuelConsumptionRoute; } // 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/screens/scaffold.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart index 0f562fa9..2caa9aed 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart @@ -22,6 +22,7 @@ class _SMSScaffoldState extends State { bool isConsumableReportSectionHovered = false; bool isStockReplenishmentSectionHovered = false; bool isStockDepletionSectionHovered = false; + bool isVehicleFuelConsumptionSectionHovered = false; @override Widget build(BuildContext context) { @@ -380,6 +381,47 @@ class _SMSScaffoldState extends State { ), ), ), + MouseRegion( + onEnter: (_) { + setState(() { + isVehicleFuelConsumptionSectionHovered = true; + }); + }, + onExit: (_) { + setState(() { + isVehicleFuelConsumptionSectionHovered = false; + }); + }, + child: Container( + decoration: BoxDecoration( + color: isVehicleFuelConsumptionSectionHovered + ? Colors.white.withOpacity(0.3) + : null, + borderRadius: BorderRadius.circular(15.0), + ), + margin: EdgeInsets.all(8.0), + child: ListTile( + leading: Icon(Icons.local_gas_station_sharp, + color: Colors.white, size: 20.0), + title: Container( + margin: EdgeInsets.only(left: 12.0), + transform: + Matrix4.translationValues(-25, 0.0, 0.0), + child: Text( + "Vehicle Fuel Consumption", + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + onTap: () { + Navigator.pop(context); // Close the drawer + routeState.go('/vehicle_fuel_consumption'); + }, + ), + ), + ), SideNavigationSection( initialSectionHoveredValue: isConsumableReportSectionHovered, 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 cee6026d..cb4415b2 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 @@ -8,6 +8,7 @@ import 'package:gallery/avinya/asset_admin/lib/screens/stock_depletion.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/screens/vehicle_fuel_consumption.dart'; import 'package:gallery/avinya/asset_admin/lib/widgets/resource_allocation_report.dart'; import '../routing.dart'; @@ -69,6 +70,12 @@ class SMSScaffoldBody extends StatelessWidget { key: ValueKey('consumable_weekly_report'), child: ConsumableWeeklyReportScreen(), ) + else if (currentRoute.pathTemplate + .startsWith('/vehicle_fuel_consumption')) + const FadeTransitionPage( + key: ValueKey('vehicle_fuel_consumption'), + child: VehicleFuelConsumptionScreen(), + ) // 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/screens/vehicle_fuel_consumption.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption.dart new file mode 100644 index 00000000..90081a9a --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption.dart'; + +class VehicleFuelConsumptionScreen extends StatefulWidget { + const VehicleFuelConsumptionScreen({super.key}); + + @override + State createState() => _VehicleFuelConsumptionScreenState(); +} + +class _VehicleFuelConsumptionScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text("Vehicle Fuel Consumption"), + ), + body: SingleChildScrollView( + child: Container( + child: VehicleFuelConsumption(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption.dart new file mode 100644 index 00000000..6691892b --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption.dart @@ -0,0 +1,333 @@ +import 'package:flutter/material.dart'; + +class VehicleFuelConsumption extends StatefulWidget { + const VehicleFuelConsumption({super.key}); + + @override + State createState() => _VehicleFuelConsumptionState(); +} + +class _VehicleFuelConsumptionState extends State { + DateTime? _selectedDate; + String reasonValue = 'Student Shuttle-Morning'; + final List reasonValues = [ + 'Student Shuttle-Morning', + 'Student Shuttle-Evening', + 'Other' + ]; + String vehicleNoValue = 'QL-9904'; + final List vehicleNoValues = ['QL-9904', 'PB-2010', 'KV-1892']; + final _starting_meter_controller = TextEditingController(); + final _ending_meter_controller = TextEditingController(); + final _comment_controller = TextEditingController(); + + Future _selectDate(BuildContext context) async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2020), + lastDate: DateTime(2101), + ); + + if (pickedDate != null && pickedDate != _selectedDate) { + setState(() { + _selectedDate = pickedDate; + }); + } + } + + @override + void dispose() { + _starting_meter_controller.dispose(); + _ending_meter_controller.dispose(); + _comment_controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var screenWidth = MediaQuery.of(context).size.width; + var screenHeight = MediaQuery.of(context).size.height; + + return Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 20.0, top: 30.0), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Select a Date :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 30.0), + width: screenWidth * 0.05, + height: screenHeight * 0.06, + child: TextField( + readOnly: true, + decoration: InputDecoration( + prefixIcon: Icon(Icons.calendar_month), + border: InputBorder.none), + onTap: () => _selectDate(context), + ), + ), + ), + if (_selectedDate != null) + Flexible( + flex: 1, + child: Text('${_selectedDate!.toLocal()}'.split(' ')[0])) + else + Flexible( + flex: 1, + child: Container( + margin: EdgeInsets.only(left: 20.0), + child: const Text( + 'No date selected.', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ), + ) + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Select a Reason :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 20.0), + width: screenWidth * 0.17, + height: screenHeight * 0.06, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey, width: 1.0), + borderRadius: BorderRadius.circular(5.0)), + child: DropdownButton( + isExpanded: true, + underline: SizedBox.shrink(), + value: reasonValue, + items: reasonValues.map((String? reason) { + return DropdownMenuItem( + value: reason, child: Text(reason ?? '')); + }).toList(), + onChanged: (String? newValue) async { + if (newValue == null) { + return; + } + setState(() { + reasonValue = newValue; + }); + }), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Vehicle No :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 50.0), + width: screenWidth * 0.08, + height: screenHeight * 0.06, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey, width: 1.0), + borderRadius: BorderRadius.circular(5.0)), + child: DropdownButton( + isExpanded: true, + underline: SizedBox.shrink(), + value: vehicleNoValue, + items: vehicleNoValues.map((String? vehicleNo) { + return DropdownMenuItem( + value: vehicleNo, child: Text(vehicleNo ?? '')); + }).toList(), + onChanged: (String? newValue) async { + if (newValue == null) { + return; + } + setState(() { + vehicleNoValue = newValue; + }); + }), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Starting Meter :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 30.0), + width: screenWidth * 0.2, + height: screenHeight * 0.08, + child: TextField( + maxLength: 20, + keyboardType: TextInputType.number, + controller: _starting_meter_controller, + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0))), + ), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Ending Meter :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 35.0), + width: screenWidth * 0.2, + height: screenHeight * 0.08, + child: TextField( + maxLength: 20, + keyboardType: TextInputType.number, + controller: _ending_meter_controller, + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0))), + ), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Flexible( + flex: 1, + child: Text( + 'Comment :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + width: 20.0, + ), + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.only(left: 55.0), + width: screenWidth * 0.2, + height: screenHeight * 0.2, + child: TextField( + keyboardType: TextInputType.multiline, + maxLines: null, + minLines: 3, + controller: _comment_controller, + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0))), + ), + ), + ), + ], + ), + Row( + children: [ + Flexible( + child: Container( + width: screenWidth * 0.2, + height: screenHeight * 0.1, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0)), + backgroundColor: Colors.yellow[700], + side: BorderSide( + color: Colors.grey.shade500, + width: 0.5, + style: BorderStyle.solid, + ), + ), + onPressed: (() {}), + child: Text( + 'Save', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w400), + )), + ), + ), + ], + ) + ]), + ), + ); + } +} From 43efe3aba7df75eeb361dd285dffaee17d03180c Mon Sep 17 00:00:00 2001 From: lahirulakruwan Date: Tue, 13 Aug 2024 11:55:21 +0530 Subject: [PATCH 3/3] Consumable dashboard UI fixes changes added --- campus/bffs/asset/api/client.bal | 2 +- campus/bffs/asset/api/types.bal | 3 + .../bffs/asset/graphql_client/asset.graphql | 1 + .../graphql_client_cg_src/client.bal | 2 +- .../graphql_client_cg_src/types.bal | 3 + .../bffs/asset/graphql_client/schema.graphql | 4 + campus/bffs/asset/graphql_client/schema.json | 40 ++ .../lib/avinya/asset_admin/lib/app.dart | 6 + .../lib/data/stock_repenishment.dart | 5 + .../asset_admin/lib/screens/scaffold.dart | 4 + .../lib/screens/scaffold_body.dart | 9 +- ...hicle_fuel_consumption_monthly_report.dart | 16 + .../lib/widgets/consumable_bar_chart.dart | 477 +++++++++--------- .../lib/widgets/consumable_dashboard.dart | 46 +- .../lib/widgets/latest_consumable_data.dart | 215 +++----- ...hicle_fuel_consumption_monthly_report.dart | 17 + 16 files changed, 470 insertions(+), 380 deletions(-) create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption_monthly_report.dart create mode 100644 campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption_monthly_report.dart diff --git a/campus/bffs/asset/api/client.bal b/campus/bffs/asset/api/client.bal index 9593653d..4737757b 100644 --- a/campus/bffs/asset/api/client.bal +++ b/campus/bffs/asset/api/client.bal @@ -233,7 +233,7 @@ public isolated client class GraphqlClient { } 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 consumable {id name} quantity quantity_in quantity_out prev_quantity resource_property {id property value} created updated}}`; + 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 prev_quantity is_below_threshold resource_property {id property value} created updated}}`; 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/api/types.bal b/campus/bffs/asset/api/types.bal index 568085c8..7ae63654 100644 --- a/campus/bffs/asset/api/types.bal +++ b/campus/bffs/asset/api/types.bal @@ -141,6 +141,7 @@ public type Consumable record { string? description?; string? model?; string? serial_number?; + anydata? threshold?; int? id?; string? updated?; string? record_type?; @@ -242,6 +243,7 @@ public type Inventory record { int? avinya_type_id?; string? description?; int? asset_id?; + int? is_below_threshold?; string? record_type?; string? manufacturer?; anydata? quantity_out?; @@ -1095,6 +1097,7 @@ public type GetInventoryDataByOrganizationResponse record {| anydata? quantity_in; anydata? quantity_out; anydata? prev_quantity; + int? is_below_threshold; record {| int? id; string? property; diff --git a/campus/bffs/asset/graphql_client/asset.graphql b/campus/bffs/asset/graphql_client/asset.graphql index c243b52a..54bd555c 100644 --- a/campus/bffs/asset/graphql_client/asset.graphql +++ b/campus/bffs/asset/graphql_client/asset.graphql @@ -655,6 +655,7 @@ query getInventoryDataByOrganization($organization_id: Int!,$date: String!) { quantity_in quantity_out prev_quantity + is_below_threshold resource_property{ id property 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 2611265e..e406b948 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 consumable {id name} quantity quantity_in quantity_out prev_quantity resource_property {id property value} created updated}}`; + 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 prev_quantity is_below_threshold resource_property {id property value} created updated}}`; 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 3810d55b..2191c623 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 @@ -144,6 +144,7 @@ public type Consumable record { string? description?; string? model?; string? serial_number?; + anydata? threshold?; int? id?; string? updated?; string? record_type?; @@ -246,6 +247,7 @@ public type Inventory record { int? avinya_type_id?; string? description?; int? asset_id?; + int? is_below_threshold?; string? record_type?; string? manufacturer?; anydata? quantity_out?; @@ -1101,6 +1103,7 @@ public type GetInventoryDataByOrganizationResponse record {| anydata? quantity_in; anydata? quantity_out; anydata? prev_quantity; + int? is_below_threshold; record {| int? id; string? property; diff --git a/campus/bffs/asset/graphql_client/schema.graphql b/campus/bffs/asset/graphql_client/schema.graphql index bb27c3a0..3dcfb4e1 100644 --- a/campus/bffs/asset/graphql_client/schema.graphql +++ b/campus/bffs/asset/graphql_client/schema.graphql @@ -327,6 +327,7 @@ input Consumable { manufacturer: String model: String serial_number: String + threshold: Decimal created: String updated: String } @@ -339,6 +340,7 @@ type ConsumableData { manufacturer: String model: String serial_number: String + threshold: Decimal created: String updated: String } @@ -558,6 +560,7 @@ input Inventory { prev_quantity: Decimal resource_property_id: Int resource_property_value: String + is_below_threshold: Int created: String updated: String } @@ -580,6 +583,7 @@ type InventoryData { month_name: String description: String manufacturer: String + is_below_threshold: Int created: String updated: String } diff --git a/campus/bffs/asset/graphql_client/schema.json b/campus/bffs/asset/graphql_client/schema.json index 220c6cf2..d86212c2 100644 --- a/campus/bffs/asset/graphql_client/schema.json +++ b/campus/bffs/asset/graphql_client/schema.json @@ -6265,6 +6265,17 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "is_below_threshold", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "created", "args": [], @@ -9123,6 +9134,15 @@ }, "defaultValue": null }, + { + "name": "is_below_threshold", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, { "name": "created", "type": { @@ -11313,6 +11333,17 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "threshold", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Decimal", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "created", "args": [], @@ -11599,6 +11630,15 @@ }, "defaultValue": null }, + { + "name": "threshold", + "type": { + "kind": "SCALAR", + "name": "Decimal", + "ofType": null + }, + "defaultValue": null + }, { "name": "created", "type": { diff --git a/campus/frontend/lib/avinya/asset_admin/lib/app.dart b/campus/frontend/lib/avinya/asset_admin/lib/app.dart index bbfa96c1..38203f13 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/app.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/app.dart @@ -42,6 +42,7 @@ class _AssetAdminSystemState extends State { '/consumable_monthly_report', '/consumable_weekly_report', '/vehicle_fuel_consumption', + '/vehicle_fuel_consumption_monthly_report', // '/assets/new', // '/assets/all', // '/assets/popular', @@ -140,6 +141,9 @@ class _AssetAdminSystemState extends State { final vehicleFuelConsumptionRoute = ParsedRoute( '/vehicle_fuel_consumption', '/vehicle_fuel_consumption', {}, {}); + + final vehicleFuelConsumptionMonthlyReportRoute = ParsedRoute( + '/vehicle_fuel_consumption_monthly_report', '/vehicle_fuel_consumption_monthly_report', {}, {}); // // Go to /apply if the user is not signed in @@ -163,6 +167,8 @@ class _AssetAdminSystemState extends State { return consumableWeeklyReportRoute; } else if (signedIn && from == vehicleFuelConsumptionRoute) { return vehicleFuelConsumptionRoute; + } else if (signedIn && from == vehicleFuelConsumptionMonthlyReportRoute) { + return vehicleFuelConsumptionMonthlyReportRoute; } // 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 218fd3ac..90870943 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 @@ -22,6 +22,7 @@ class StockReplenishment { double? quantity_in; double? quantity_out; double? total_quantity; + int? is_below_threshold; ResourceProperty? resource_property; Consumable? consumable; @@ -39,6 +40,7 @@ class StockReplenishment { this.quantity_in, this.quantity_out, this.total_quantity, + this.is_below_threshold, this.resource_property, this.consumable}); @@ -61,6 +63,8 @@ class StockReplenishment { prev_quantity: json["prev_quantity"] == null ? null : json["prev_quantity"], total_quantity: json["quantity"] == null ? null : json["quantity"], + is_below_threshold: + json["is_below_threshold"] == null ? null : json["is_below_threshold"], quantity_in: json["quantity_in"] == null ? null : json["quantity_in"], quantity_out: json["quantity_out"] == null ? null : json["quantity_out"], @@ -85,6 +89,7 @@ class StockReplenishment { "total_quantity": quantity == null ? null : quantity, "quantity_in": quantity_in == null ? null : quantity_in, "quantity_out": quantity_out == null ? null : quantity_out, + "is_below_threshold":is_below_threshold == null ? null : is_below_threshold, "resource_property": resource_property == null ? null : resource_property, "consumable": consumable == null ? null : consumable, 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 2caa9aed..33056095 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/scaffold.dart @@ -47,6 +47,10 @@ class _SMSScaffoldState extends State { tileName: "Consumable Monthly Report", route: "/consumable_monthly_report", icon: Icons.summarize_sharp), + SideNavigationSectionTile( + tileName: "Vehicle Fuel Consumption Monthly Report", + route: "/vehicle_fuel_consumption_monthly_report", + icon: Icons.local_gas_station_outlined), ]; return Scaffold( 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 cb4415b2..29af18c2 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 @@ -10,6 +10,7 @@ import 'package:gallery/avinya/asset_admin/lib/screens/consumable_monthly_report import 'package:gallery/avinya/asset_admin/lib/screens/consumable_weekly_report.dart'; import 'package:gallery/avinya/asset_admin/lib/screens/vehicle_fuel_consumption.dart'; import 'package:gallery/avinya/asset_admin/lib/widgets/resource_allocation_report.dart'; +import 'package:gallery/avinya/asset_admin/lib/screens/vehicle_fuel_consumption_monthly_report.dart'; import '../routing.dart'; import '../widgets/fade_transition_page.dart'; @@ -70,12 +71,18 @@ class SMSScaffoldBody extends StatelessWidget { key: ValueKey('consumable_weekly_report'), child: ConsumableWeeklyReportScreen(), ) - else if (currentRoute.pathTemplate + else if (currentRoute.pathTemplate .startsWith('/vehicle_fuel_consumption')) const FadeTransitionPage( key: ValueKey('vehicle_fuel_consumption'), child: VehicleFuelConsumptionScreen(), ) + else if (currentRoute.pathTemplate + .startsWith('/vehicle_fuel_consumption_monthly_report')) + const FadeTransitionPage( + key: ValueKey('vehicle_fuel_consumption_monthly_report'), + child: VehicleFuelConsumptionMonthlyReportScreen(), + ) // 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/screens/vehicle_fuel_consumption_monthly_report.dart b/campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption_monthly_report.dart new file mode 100644 index 00000000..8a3d668e --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/screens/vehicle_fuel_consumption_monthly_report.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption_monthly_report.dart'; + +class VehicleFuelConsumptionMonthlyReportScreen extends StatefulWidget { + const VehicleFuelConsumptionMonthlyReportScreen({super.key}); + + @override + State createState() => _VehicleFuelConsumptionMonthlyReportScreenState(); +} + +class _VehicleFuelConsumptionMonthlyReportScreenState extends State { + @override + Widget build(BuildContext context) { + return const VehicleFuelConsumptionMonthlyReport(); + } +} \ No newline at end of file diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart index 89823c93..7c6a277e 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_bar_chart.dart @@ -213,248 +213,271 @@ class _ConsumableBarChartState extends State { @override Widget build(BuildContext context) { - return AspectRatio( - aspectRatio: 16 / 9, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Center( - child: Text( - 'Consumable Yearly Report', + var screenWidth = MediaQuery.of(context).size.width; + var screenHeight = MediaQuery.of(context).size.height; + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Center( + child: Text( + 'Consumable Yearly Report', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + Row( + //mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Select a Consumable :', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + SizedBox( + width: 5.0, + ), + FutureBuilder>( + future: _fetchConsumableData, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Container( + margin: EdgeInsets.only(top: 10), + child: SpinKitCircle( + color: (Colors.yellow[700]), + size: 70, + ), + ); + } else if (snapshot.hasError) { + return const Center( + child: Text('Something went wrong...'), + ); + } else if (!snapshot.hasData) { + return const Center( + child: Text('No consumable data found'), + ); + } + final consumableData = snapshot.data!; + return DropdownButton( + value: _selectedConsumableValue, + items: consumableData.map((Consumable consumable) { + return DropdownMenuItem( + value: consumable, + child: Text(consumable.name! ?? '')); + }).toList(), + onChanged: (Consumable? newValue) async { + if (newValue == null) { + return; + } + + int consumableId = newValue.id!; + + int? parentOrgId = campusAppsPortalInstance + .getUserPerson() + .organization! + .id; + + _fetchedConsumableYearlySummaryData = + await getConsumableYearlyReport( + parentOrgId!, consumableId, _selectedYear.year); + + _selectedConsumableUnitValue = + _fetchedConsumableYearlySummaryData + .first.resource_property!.value; + + _consumableItemQuantityInForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_in!) + .toList(); + _consumableItemQuantityOutForYear = + _fetchedConsumableYearlySummaryData + .map((consumableMonthObject) => + consumableMonthObject.quantity_out!) + .toList(); + + setState(() { + _fetchedConsumableYearlySummaryData; + _selectedConsumableValue = newValue; + _consumableItemQuantityInForYear; + _consumableItemQuantityOutForYear; + }); + }); + }), + const SizedBox( + width: 20.0, + ), + Text( + 'Select a Year:', + style: TextStyle( + fontStyle: FontStyle.normal, fontWeight: FontWeight.bold), + ), + SizedBox( + width: 5, + ), + TextButton( + onPressed: () => _showPicker(context), + child: Icon(Icons.calendar_month), + ), + if (_selectedYear == null) + Container( + margin: EdgeInsets.only(left: 10.0), + child: const Text( + 'No year selected.', style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.purple, // Matches yellow[700] + fontSize: 14, + fontWeight: FontWeight.normal, ), ), + ) + else + Container( + child: Text( + _selectedYear.year.toString(), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.normal), + ), ), - Flexible( - flex: 2, - child: Row( - children: [ - Text( - 'Select a Consumable :', - style: TextStyle( - fontStyle: FontStyle.normal, - fontWeight: FontWeight.bold), - ), - FutureBuilder>( - future: _fetchConsumableData, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return Container( - margin: EdgeInsets.only(top: 10), - child: SpinKitCircle( - color: (Colors.yellow[700]), - size: 70, - ), - ); - } else if (snapshot.hasError) { - return const Center( - child: Text('Something went wrong...'), - ); - } else if (!snapshot.hasData) { - return const Center( - child: Text('No consumable data found'), - ); - } - final consumableData = snapshot.data!; - return DropdownButton( - value: _selectedConsumableValue, - items: consumableData.map((Consumable consumable) { - return DropdownMenuItem( - value: consumable, - child: Text(consumable.name! ?? '')); - }).toList(), - onChanged: (Consumable? newValue) async { - if (newValue == null) { - return; - } - - int consumableId = newValue.id!; - - int? parentOrgId = campusAppsPortalInstance - .getUserPerson() - .organization! - .id; - - _fetchedConsumableYearlySummaryData = - await getConsumableYearlyReport(parentOrgId!, - consumableId, _selectedYear.year); - - _selectedConsumableUnitValue = - _fetchedConsumableYearlySummaryData - .first.resource_property!.value; - - _consumableItemQuantityInForYear = - _fetchedConsumableYearlySummaryData - .map((consumableMonthObject) => - consumableMonthObject.quantity_in!) - .toList(); - _consumableItemQuantityOutForYear = - _fetchedConsumableYearlySummaryData - .map((consumableMonthObject) => - consumableMonthObject.quantity_out!) - .toList(); - - setState(() { - _fetchedConsumableYearlySummaryData; - _selectedConsumableValue = newValue; - _consumableItemQuantityInForYear; - _consumableItemQuantityOutForYear; - }); - }); - }), - const SizedBox( - width: 20.0, - ), - Text( - 'Select a Year:', + SizedBox( + width: 20.0, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ), + SizedBox( + width: 5, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ), + ], + ), + const SizedBox( + height: 15.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Text( + 'Unit - ${_selectedConsumableUnitValue ?? ''}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox( + width: 20, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 70.0, + height: 30.0, + child: Padding( + padding: const EdgeInsets.only(top: 5.0), + child: Text( + 'Quantity In', style: TextStyle( - fontStyle: FontStyle.normal, - fontWeight: FontWeight.bold), - ), - SizedBox( - width: 5, - ), - TextButton( - onPressed: () => _showPicker(context), - child: Icon(Icons.calendar_month), - ), - if (_selectedYear == null) - Container( - margin: EdgeInsets.only(left: 10.0), - child: const Text( - 'No year selected.', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - ), - ), - ) - else - Container( - child: Text( - _selectedYear.year.toString(), - style: TextStyle( - fontSize: 14, fontWeight: FontWeight.normal), - ), + fontSize: 14, + fontWeight: FontWeight.normal, ), - SizedBox( - width: 20.0, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 70.0, - height: 30.0, - child: Text( - 'Quantity In', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - ), - ), - ), - SizedBox( - width: 5, - ), - SizedBox( - child: Container( - width: 20, - height: 20, - color: Colors.green, - ), - ), - ], - ), - SizedBox( - width: 5, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 80.0, - height: 30.0, - child: Text( - 'Quantity Out', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - ), - ), - ), - SizedBox( - width: 5, - ), - SizedBox( - child: Container( - width: 20, - height: 20, - color: Colors.red, - ), - ), - ], ), - ], + ), ), - ), - const SizedBox( - height: 5.0, - ), - Center( - child: Text( - 'Unit - ${_selectedConsumableUnitValue ?? ''}', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + SizedBox( + width: 5, + ), + SizedBox( + child: Container( + width: 20, + height: 20, + color: Colors.green, ), ), - ), - const SizedBox( - height: 20.0, - ), - if (_consumableItemQuantityInForYear.length > 0 && - _consumableItemQuantityOutForYear.length > 0) - Flexible( - flex: 3, - child: BarChart( - BarChartData( - groupsSpace: 30.0, - alignment: BarChartAlignment.spaceAround, - barGroups: _getBarGroups(), - titlesData: FlTitlesData( - show: true, - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - getTitlesWidget: getTitles, - showTitles: true, - reservedSize: 50))), - gridData: FlGridData( - show: true, - drawHorizontalLine: true, - drawVerticalLine: false), + ], + ), + SizedBox( + width: 15.0, + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80.0, + height: 30.0, + child: Text( + 'Quantity Out', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), ), ), - ) - else - Container( - margin: EdgeInsets.only(left: 10.0), - child: const Text( - 'No data', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, + SizedBox( + width: 5, + ), + SizedBox( + child: Container( + width: 20, + height: 20, + color: Colors.red, ), ), - ) - ], + ], + ), + ), + ], + ), + const SizedBox( + height: 20.0, + ), + if (_consumableItemQuantityInForYear.length > 0 && + _consumableItemQuantityOutForYear.length > 0) + Container( + height: screenHeight * 0.5, + child: BarChart( + BarChartData( + groupsSpace: 30.0, + alignment: BarChartAlignment.spaceAround, + barGroups: _getBarGroups(), + titlesData: FlTitlesData( + show: true, + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + getTitlesWidget: getTitles, + showTitles: true, + reservedSize: 50))), + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: false), + ), + ), + ) + else + Container( + margin: EdgeInsets.only(left: 10.0), + child: const Text( + 'No data', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), ), + ], ); + // ); } } diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart index e964804f..ec53f7aa 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/consumable_dashboard.dart @@ -14,24 +14,38 @@ class ConsumableDashboard extends StatefulWidget { class _ConsumableDashboardState extends State { @override Widget build(BuildContext context) { - return Row( - children: [ - Flexible( - flex: 2, - child: Padding( - padding: const EdgeInsets.only(left: 20, top: 25.0), - child: LatestConsumableData(), - ), - ), - Flexible( - flex: 3, + var screenWidth = MediaQuery.of(context).size.width; + var screenHeight = MediaQuery.of(context).size.height; + + return Container( + color: Colors.grey[200], + child: Row( + children: [ + Flexible( + flex: 2, child: Padding( padding: const EdgeInsets.only(left: 20, top: 25.0), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConsumableBarChart()), - )) - ], + child: Container( + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(5.0)), + // color: Colors.white, + child: LatestConsumableData()), + ), + ), + Flexible( + flex: 3, + child: Padding( + padding: const EdgeInsets.only(left: 15, top: 25.0, right: 5.0), + child: Container( + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(5.0)), + //color: Colors.white, + width: screenWidth * 0.8, + height: screenHeight * 0.8, + child: ConsumableBarChart()), + )) + ], + ), ); } } diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart index 4d571728..10892807 100644 --- a/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/latest_consumable_data.dart @@ -14,7 +14,6 @@ class LatestConsumableData extends StatefulWidget { } class _LatestConsumableDataState extends State { - List _fetchedLatestConsumableData = []; bool _isFetching = false; DateTime? _selected; @@ -33,8 +32,7 @@ class _LatestConsumableDataState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _data = MyData(_fetchedLatestConsumableData,isQuantityBelowThreshold, - getThresholdValue); + _data = MyData(_fetchedLatestConsumableData); } Future _fetchInitialData() async { @@ -45,14 +43,13 @@ class _LatestConsumableDataState extends State { _isFetching = true; // Show loading indicator }); try { - _fetchedLatestConsumableData = await getStockListforReplenishment(parentOrgId, - DateFormat('yyyy-MM-dd').format(DateTime.now())); + _fetchedLatestConsumableData = await getStockListforReplenishment( + parentOrgId, DateFormat('yyyy-MM-dd').format(DateTime.now())); - setState(() { - _isFetching = false; - _data = MyData(_fetchedLatestConsumableData, isQuantityBelowThreshold, - getThresholdValue); - }); + setState(() { + _isFetching = false; + _data = MyData(_fetchedLatestConsumableData); + }); } catch (error) { // Handle any errors that occur during the fetch // You can show an error message or take appropriate actions here @@ -64,164 +61,114 @@ class _LatestConsumableDataState extends State { } } - bool isQuantityBelowThreshold(String unit,double quantity){ - - const Map thresholds = { - 'kg': 2.0, - 'g': 500.0, - 'packet': 2.0, - 'cup': 10.0, - 'bags': 10.0, - 'rolls': 1.0, - 'litre': 2.0, - 'ml': 100.0, - 'piece': 20.0, - }; - - if(thresholds.containsKey(unit)){ - return quantity < thresholds[unit]!; - } - return false; - } - - double getThresholdValue(String unit){ - - const Map thresholds = { - 'kg': 2.0, - 'g': 500.0, - 'packet': 2.0, - 'cup': 10.0, - 'bags': 10.0, - 'rolls': 1.0, - 'litre': 2.0, - 'ml': 100.0, - 'piece': 20.0, - }; - - return thresholds.containsKey(unit) ? thresholds[unit]! : 0.0; - } - List _buildDataColumns() { List columnNames = []; columnNames.add(DataColumn( label: Center( - child: Text('Product Name', - style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), - ))); - - columnNames.add(DataColumn( - label: Center( - child: Text('On Hand(QTY)', - style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), - ))); + child: Text('Product Name', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); columnNames.add(DataColumn( label: Center( - child: Text('Unit', - style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), - ))); + child: Text('On Hand(QTY)', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ))); columnNames.add(DataColumn( label: Center( - child: Text('Threshold', + child: Text('Unit', + textAlign: TextAlign.center, style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), - ))); + ))); return columnNames; } @override Widget build(BuildContext context) { + var screenWidth = MediaQuery.of(context).size.width; + var screenHeight = MediaQuery.of(context).size.height; + return SingleChildScrollView( child: Wrap(children: [ - Center( - child: Text( - 'Latest Consumable Data', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.purple, - ), - ), - ), - if (_isFetching) - Container( - margin: EdgeInsets.only(top: 180), - child: SpinKitCircle( - color: (Colors - .yellow[700]), - size: 50, - ), - ) - else if (_fetchedLatestConsumableData.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(), - columnSpacing:35, - horizontalMargin: 30, - rowsPerPage: 10, - ), - ), - ) - else - Container( - margin: EdgeInsets.all(20), - child: Text('No latest consumable data found'), - ), - ]), + Center( + child: Text( + 'Latest Consumable Data', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_isFetching) + Container( + margin: EdgeInsets.only(top: 180), + child: SpinKitCircle( + color: (Colors.yellow[700]), + size: 50, + ), + ) + else if (_fetchedLatestConsumableData.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: Container( + width: screenWidth * 0.95, + height: screenHeight * 1.2, + child: PaginatedDataTable( + showCheckboxColumn: false, + source: _data, + columns: _buildDataColumns(), + columnSpacing: 50, + rowsPerPage: 10, + ), + ), + ), + ) + else + Container( + margin: EdgeInsets.all(20), + child: Text('No latest consumable data found'), + ), + ]), ); } } class MyData extends DataTableSource { - MyData(this._fetchedLatestConsumableData,this.isQuantityBelowThreshold,this.getThresholdValue); + MyData(this._fetchedLatestConsumableData); final List _fetchedLatestConsumableData; - final Function(String,double) isQuantityBelowThreshold; - final Function(String) getThresholdValue; @override DataRow? getRow(int index) { - var consumableItem = _fetchedLatestConsumableData[index]; - bool isBelowThreshold = isQuantityBelowThreshold( - consumableItem.resource_property!.value.toString(), - consumableItem.quantity! - ); - - double threshold = getThresholdValue(consumableItem.resource_property!.value!); - - List cells = List.filled(4, DataCell.empty); - - cells[0] = DataCell(Center( - child: Text(consumableItem.consumable!.name.toString()))); - cells[1] = DataCell( - Center(child: Text(consumableItem.quantity.toString()))); - cells[2] = - DataCell( - Center(child: Text(consumableItem.resource_property!.value.toString()))); - cells[3] = - DataCell( - Center(child: Text(threshold.toString()))); - + List cells = List.filled(3, DataCell.empty); + + cells[0] = DataCell( + Center(child: Text(consumableItem.consumable!.name.toString()))); + cells[1] = + DataCell(Center(child: Text(consumableItem.quantity.toString()))); + cells[2] = DataCell(Center( + child: Text(consumableItem.resource_property!.value.toString()))); return DataRow( - color: MaterialStateProperty.resolveWith((Set states){ - return isBelowThreshold ?Colors.red[300]:null; - }), - cells: cells - ); + color: MaterialStateProperty.resolveWith( + (Set states) { + return consumableItem.is_below_threshold == 1 + ? Colors.red[300] + : null; + }), + cells: cells); } @override diff --git a/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption_monthly_report.dart b/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption_monthly_report.dart new file mode 100644 index 00000000..1f5fc90a --- /dev/null +++ b/campus/frontend/lib/avinya/asset_admin/lib/widgets/vehicle_fuel_consumption_monthly_report.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class VehicleFuelConsumptionMonthlyReport extends StatefulWidget { + const VehicleFuelConsumptionMonthlyReport({super.key}); + + @override + State createState() => _VehicleFuelConsumptionMonthlyReportState(); +} + +class _VehicleFuelConsumptionMonthlyReportState extends State { + @override + Widget build(BuildContext context) { + return Text( + 'VehicleFuelConsumptionMonthlyReport' + ); + } +} \ No newline at end of file