From 2e27f76f0403bfc5e256f28a424871f73a0f3ace Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:15:02 +0300 Subject: [PATCH] site apis implemented --- lib/controllers/api_routes/urls.dart | 2 +- .../providers/site_request_provider.dart | 119 ++++-- .../screens/site_request_form_screen.dart | 47 ++- .../screens/site_request_history_screen.dart | 375 +++++++----------- .../multiple_item_drop_down_menu.dart | 24 +- 5 files changed, 281 insertions(+), 286 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index f42ccf0a..bacfe450 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -63,7 +63,7 @@ class URLs { // Site Request APIs static get getSiteRequestTypes => "$_baseUrl/Lookups/GetLookup?lookupEnum=4016"; // get site request types static get submitSiteRequest => "$_baseUrl/SiteRequest/CreateSiteRequest"; // post submit site request - static get getSiteRequestHistory => "$_baseUrl/SiteRequest/GetSiteRequestHistory"; // get site request history + static get getSiteRequests => "$_baseUrl/SiteRequest/GetSiteRequests"; // get site request history static get getDepartmentsBySites => "$_baseUrl/Customer/GetDepartmentsBySites"; // post get departments by sites // static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login diff --git a/lib/modules/site_request_module/providers/site_request_provider.dart b/lib/modules/site_request_module/providers/site_request_provider.dart index dd2a880a..31178614 100644 --- a/lib/modules/site_request_module/providers/site_request_provider.dart +++ b/lib/modules/site_request_module/providers/site_request_provider.dart @@ -53,17 +53,11 @@ class SiteRequestProvider extends ChangeNotifier { } List getCurrentUserAssignedSites(UserProvider userProvider) { - // You can modify this based on how assigned sites are stored in your User model - // For now, returning empty list as it's not clear from the User model - // You may need to fetch this from another source or API return []; } - // Request types from API List _requestTypes = []; List get requestTypes => _requestTypes; - - // Fetch request types from API (Step 1) Future fetchRequestTypes() async { if (_loading) return -2; @@ -112,12 +106,8 @@ class SiteRequestProvider extends ChangeNotifier { notifyListeners(); return response.statusCode; } - - // Mock sites as Base models List _allSites = []; List get allSites => _allSites; - - // Fetch sites from API (Step 2) Future fetchSites() async { if (_loading) return -2; @@ -221,6 +211,16 @@ class SiteRequestProvider extends ChangeNotifier { final List _requestHistory = []; List get requestHistory => _requestHistory; + // Pagination state + int _currentPage = 1; + int _pageSize = 10; + bool _hasMoreData = true; + bool _isLoadingMore = false; + + int get currentPage => _currentPage; + bool get hasMoreData => _hasMoreData; + bool get isLoadingMore => _isLoadingMore; + // Get available sites based on request type List getAvailableSites(String requestType, List assignedSites) { if (requestType == 'Remove Site') { @@ -324,50 +324,81 @@ class SiteRequestProvider extends ChangeNotifier { } } - // Fetch request history from API (Step 5) - Future fetchRequestHistory() async { - if (_loading) return -2; + // Fetch paginated site requests + Future fetchSiteRequests({int? pageSize, int? pageNumber, bool loadMore = false}) async { + final size = pageSize ?? _pageSize; + final page = pageNumber ?? (loadMore ? _currentPage + 1 : 1); + + if (size <= 0 || page < 1) { + _errorMessage = 'Invalid pagination parameters'; + notifyListeners(); + return; + } + + // Set loading state + if (loadMore) { + _isLoadingMore = true; + } else { + loading = true; + _currentPage = 1; + _hasMoreData = true; + _requestHistory.clear(); + } - _loading = true; _errorMessage = null; notifyListeners(); - late Response response; try { - response = await ApiManager.instance.get( - URLs.getSiteRequestHistory, - enableToastMessage: false, + final response = await ApiManager.instance.post( + URLs.getSiteRequests, + body: { + "pageSize": size, + "pageNumber": page, + }, ); - } catch (error) { - _loading = false; - stateCode = -1; - _errorMessage = 'Failed to fetch request history'; - notifyListeners(); - return -1; - } - stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - try { - var responseData = json.decode(response.body); - List listJson = responseData is List ? responseData : responseData['data'] ?? []; - _requestHistory.clear(); - _requestHistory.addAll( - listJson.map((item) => SiteRequestModel.fromJson(item)).toList() - ); - } catch (e) { - _errorMessage = 'Failed to parse request history'; - _loading = false; - notifyListeners(); - return -1; + if (response.statusCode == 200) { + final data = json.decode(response.body); + + if (data['isSuccess'] == true) { + List siteRequests = (data['data'] as List?) + ?.map((item) => SiteRequestModel.fromJson(item)) + .toList() ?? []; + + if (loadMore) { + _requestHistory.addAll(siteRequests); + } else { + _requestHistory.clear(); + _requestHistory.addAll(siteRequests); + } + + // Update pagination state + _currentPage = page; + _hasMoreData = siteRequests.length >= size; + } else { + _errorMessage = data['message'] ?? 'Failed to fetch site requests'; + } + } else { + _errorMessage = 'Failed to fetch site requests'; } - } else { - _errorMessage = 'Failed to fetch request history'; + } catch (e) { + _errorMessage = 'Error: ${e.toString()}'; + } finally { + loading = false; + _isLoadingMore = false; + notifyListeners(); } + } - _loading = false; - notifyListeners(); - return response.statusCode; + // Load more site requests (for pagination) + Future loadMoreSiteRequests() async { + if (!_hasMoreData || _isLoadingMore) return; + await fetchSiteRequests(loadMore: true); + } + + // Refresh site requests (reset to first page) + Future refreshSiteRequests() async { + await fetchSiteRequests(pageNumber: 1, pageSize: _pageSize); } // Reset provider state diff --git a/lib/modules/site_request_module/screens/site_request_form_screen.dart b/lib/modules/site_request_module/screens/site_request_form_screen.dart index 638ea95a..5e5cad1f 100644 --- a/lib/modules/site_request_module/screens/site_request_form_screen.dart +++ b/lib/modules/site_request_module/screens/site_request_form_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; @@ -57,6 +59,12 @@ class _SiteRequestFormScreenState extends State { WidgetsBinding.instance.addPostFrameCallback((_) async { _provider = Provider.of(context, listen: false); + // Initialize department provider with empty state + final departmentProvider = Provider.of(context, listen: false); + departmentProvider.setSiteIds([]); + departmentProvider.items = []; + departmentProvider.loading = false; + // Fetch only request types (sites and departments are loaded by their providers) await _provider.fetchRequestTypes(); @@ -161,11 +169,31 @@ class _SiteRequestFormScreenState extends State { children: [ InfoHeader16Widget("Requester Information"), 8.height, - InfoTextWidget(label: "Full Name",value: _provider.getCurrentUserFullName(_userProvider),showEmptyValue: true,), - InfoTextWidget(label: "Email Address",value: _provider.getCurrentUserEmail(_userProvider),showEmptyValue: true,), - InfoTextWidget(label: "Mobile Number",value: _provider.getCurrentUserMobile(_userProvider),showEmptyValue: true,), - InfoTextWidget(label: "Extension Number", value: _provider.getCurrentUserExtension(_userProvider),showEmptyValue: true,), - InfoTextWidget(label: "Role",value: _provider.getCurrentUserRole(_userProvider),showEmptyValue: true,), + InfoTextWidget( + label: "Full Name", + value: _provider.getCurrentUserFullName(_userProvider), + showEmptyValue: true, + ), + InfoTextWidget( + label: "Email Address", + value: _provider.getCurrentUserEmail(_userProvider), + showEmptyValue: true, + ), + InfoTextWidget( + label: "Mobile Number", + value: _provider.getCurrentUserMobile(_userProvider), + showEmptyValue: true, + ), + InfoTextWidget( + label: "Extension Number", + value: _provider.getCurrentUserExtension(_userProvider), + showEmptyValue: true, + ), + InfoTextWidget( + label: "Role", + value: _provider.getCurrentUserRole(_userProvider), + showEmptyValue: true, + ), ], ).toShadowContainer(context); } @@ -229,6 +257,10 @@ class _SiteRequestFormScreenState extends State { } Widget _buildDepartmentMultiSelect() { + // Enable department field only when sites are selected + final isDepartmentEnabled = _selectedSites.isNotEmpty; + log('Selected Sites: ${_selectedSites.length}, isDepartmentEnabled: $isDepartmentEnabled'); + return MultipleItemDropDownMenu( context: context, title: "Departments", @@ -238,6 +270,8 @@ class _SiteRequestFormScreenState extends State { showShadow: true, height: 56.toScreenHeight, showCancel: true, + enabled: isDepartmentEnabled, // Enable only when sites are selected + loading: !isDepartmentEnabled ? false : null, // Don't show loading when disabled onSelect: (value) { if (value != null) { setState(() { @@ -291,7 +325,8 @@ class _SiteRequestFormScreenState extends State { } final request = SiteRequestModel( - requestType: _selectedRequestType!.value?.toString() ?? '', // Use value instead of id + requestType: _selectedRequestType!.value?.toString() ?? '', + // Use value instead of id employeeId: _provider.getCurrentUserEmployeeId(_userProvider), fullName: _provider.getCurrentUserFullName(_userProvider), emailAddress: _provider.getCurrentUserEmail(_userProvider), diff --git a/lib/modules/site_request_module/screens/site_request_history_screen.dart b/lib/modules/site_request_module/screens/site_request_history_screen.dart index 0aee6559..dcf82692 100644 --- a/lib/modules/site_request_module/screens/site_request_history_screen.dart +++ b/lib/modules/site_request_module/screens/site_request_history_screen.dart @@ -9,7 +9,13 @@ import 'package:test_sa/modules/site_request_module/models/site_request_model.da import 'package:test_sa/modules/site_request_module/providers/site_request_provider.dart'; import 'package:test_sa/modules/site_request_module/screens/site_request_form_screen.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_styles.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/item_views/info_date_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; class SiteRequestHistoryScreen extends StatefulWidget { static const String routeName = "/site_request_history"; @@ -24,20 +30,34 @@ class _SiteRequestHistoryScreenState extends State { late SiteRequestProvider _provider; late UserProvider _userProvider; bool _isLoadingData = true; + late ScrollController _scrollController; @override void initState() { super.initState(); + _scrollController = ScrollController(); + _scrollController.addListener(_onScroll); _loadHistoryData(); } + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.9) { + _provider.loadMoreSiteRequests(); + } + } + Future _loadHistoryData() async { WidgetsBinding.instance.addPostFrameCallback((_) async { _provider = Provider.of(context, listen: false); _userProvider = Provider.of(context, listen: false); - - // Fetch request history from API - await _provider.fetchRequestHistory(); + await _provider.fetchSiteRequests(pageNumber: 1, pageSize: 10); if (mounted) { setState(() { @@ -47,6 +67,10 @@ class _SiteRequestHistoryScreenState extends State { }); } + Future _refreshData() async { + await _provider.refreshSiteRequests(); + } + @override Widget build(BuildContext context) { _provider = Provider.of(context); @@ -88,14 +112,26 @@ class _SiteRequestHistoryScreenState extends State { body: _isLoadingData ? const Center(child: CircularProgressIndicator()) : _provider.requestHistory.isEmpty - ? _buildEmptyState() - : ListView.builder( - padding: EdgeInsets.all(16.toScreenWidth), - itemCount: _provider.requestHistory.length, - itemBuilder: (context, index) { - final request = _provider.requestHistory[index]; - return _buildRequestCard(request); - }, + ? const NoDataFound().center + : RefreshIndicator( + onRefresh: _refreshData, + child: ListView.builder( + controller: _scrollController, + padding: EdgeInsets.all(16.toScreenWidth), + itemCount: _provider.requestHistory.length + (_provider.isLoadingMore ? 1 : 0), + itemBuilder: (context, index) { + // Show loading indicator at the bottom + if (index == _provider.requestHistory.length) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 16.toScreenHeight), + child: const Center(child: CircularProgressIndicator()), + ); + } + + final request = _provider.requestHistory[index]; + return _buildRequestCard(request); + }, + ), ), ); } @@ -112,266 +148,143 @@ class _SiteRequestHistoryScreenState extends State { ), 16.height, "No site requests found".heading5(context).custom( - color: AppColor.labelTextStyleColor(context), - ), + color: AppColor.labelTextStyleColor(context), + ), 8.height, "Your submitted requests will appear here".bodyText(context).custom( - color: AppColor.lightTextColor(context), - ), + color: AppColor.lightTextColor(context), + ), ], ), ); } Widget _buildRequestCard(SiteRequestModel request) { - return Container( - margin: EdgeInsets.only(bottom: 16.toScreenHeight), - decoration: BoxDecoration( - color: AppColor.background(context), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Padding( - padding: EdgeInsets.all(16.toScreenWidth), - child: Column( + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status labels and date - matching ServiceRequestItemView layout + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status and Date Row - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - _buildStatusLabel(request.status), - _buildRequestTypeLabel(request.requestType), - ], - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - DateFormat('dd MMM yyyy').format(request.requestDate), - style: TextStyle( - fontSize: 12, - color: AppColor.labelTextStyleColor(context), - ), - ), - Text( - DateFormat('hh:mm a').format(request.requestDate), - style: TextStyle( - fontSize: 10, - color: AppColor.lightTextColor(context), - ), - ), - ], - ), - ], + StatusLabel( + label: request.status, + textColor: _getStatusTextColor(request.status), + backgroundColor: _getStatusColor(request.status), ), - 16.height, + 8.width, + StatusLabel( + label: request.requestType, + textColor: AppColor.primary10, + backgroundColor: AppColor.primary10.withValues(alpha: 0.1), + ), + 1.width.expanded, + InfoDateWidget(request.requestDate.toString()), + ], + ), - // Request ID - if (request.id != null) ...[ - _buildInfoRow("Request ID", request.id!), - 12.height, - ], + // Request header + InfoHeaderWidget("Site Request #${request.id ?? 'N/A'}"), - Divider(color: AppColor.border2Color(context)), - 12.height, + // Request details using InfoTextWidget + InfoTextWidget(label: "Full Name", value: request.fullName), + InfoTextWidget(label: "Employee ID", value: request.employeeId), + InfoTextWidget(label: "Email", value: request.emailAddress), + InfoTextWidget(label: "Mobile", value: request.mobileNumber, showEmptyValue: true), + if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty) + InfoTextWidget(label: "Extension", value: request.extensionNumber!, showEmptyValue: true), + InfoTextWidget(label: "Role", value: request.role), - // Requester Information Section - "Requester Information".heading6(context).custom( - color: AppColor.textStyleColor(context), - fontWeight: FontWeight.w600, + // Sites section with chips + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100.toScreenWidth, + child: Text( + "Sites:", + style: TextStyle( + fontSize: 14, + color: AppColor.labelTextStyleColor(context), + fontWeight: FontWeight.w500, + ), + ), ), - 12.height, - _buildInfoRow("Full Name", request.fullName), - _buildInfoRow("Employee ID", request.employeeId), - _buildInfoRow("Email", request.emailAddress), - _buildInfoRow("Mobile", request.mobileNumber), - if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty) - _buildInfoRow("Extension", request.extensionNumber!), - _buildInfoRow("Role", request.role), - - 16.height, - Divider(color: AppColor.border2Color(context)), - 12.height, - - // Site Information Section - "Site Information".heading6(context).custom( - color: AppColor.textStyleColor(context), - fontWeight: FontWeight.w600, + Expanded( + child: Wrap( + runSpacing: 4.toScreenHeight, + spacing: 4.toScreenWidth, + children: request.siteNames.map((site) => _buildChip(site)).toList(), + ), ), - 12.height, - _buildInfoRow("Request Type", request.requestType), - 12.height, + ], + ).paddingOnly(bottom: 8), - // Sites List - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Sites:", + // Departments section with chips (if available) + if (request.departmentNames != null && request.departmentNames!.isNotEmpty) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100.toScreenWidth, + child: Text( + "Departments:", style: TextStyle( fontSize: 14, color: AppColor.labelTextStyleColor(context), fontWeight: FontWeight.w500, ), ), - 8.width, - Expanded( - child: Wrap( - spacing: 6, - runSpacing: 6, - children: request.siteNames.map((site) => _buildChip(site)).toList(), - ), + ), + Expanded( + child: Wrap( + runSpacing: 4.toScreenHeight, + spacing: 4.toScreenWidth, + children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(), ), - ], - ), - - // Departments List (if available) - if (request.departmentNames != null && request.departmentNames!.isNotEmpty) ...[ - 12.height, - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Departments:", - style: TextStyle( - fontSize: 14, - color: AppColor.labelTextStyleColor(context), - fontWeight: FontWeight.w500, - ), - ), - 8.width, - Expanded( - child: Wrap( - spacing: 6, - runSpacing: 6, - children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(), - ), - ), - ], ), ], - ], - ), - ), - ); + ), + ], + ).toShadowContainer(context).paddingOnly(bottom: 16); } - Widget _buildStatusLabel(String status) { - Color backgroundColor; - Color textColor; - + Color _getStatusColor(String status) { switch (status.toLowerCase()) { case 'approved': - backgroundColor = AppColor.greenStatusColor; - textColor = AppColor.greenStatusTextColor; - break; + return AppColor.greenStatusColor; case 'rejected': - backgroundColor = AppColor.redStatusColor; - textColor = AppColor.redStatusTextColor; - break; + return AppColor.redStatusColor; case 'pending': default: - backgroundColor = AppColor.yellowStatusColor; - textColor = AppColor.yellowStatusTextColor; - break; + return AppColor.yellowStatusColor; } - - return Container( - padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 6.toScreenHeight), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - status, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: textColor, - ), - ), - ); - } - - Widget _buildRequestTypeLabel(String requestType) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 6.toScreenHeight), - decoration: BoxDecoration( - color: AppColor.primary10.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), - border: Border.all(color: AppColor.primary10.withValues(alpha: 0.3)), - ), - child: Text( - requestType, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: AppColor.primary10, - ), - ), - ); } - Widget _buildInfoRow(String label, String value) { - return Padding( - padding: EdgeInsets.only(bottom: 8.toScreenHeight), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 120.toScreenWidth, - child: Text( - "$label:", - style: TextStyle( - fontSize: 14, - color: AppColor.labelTextStyleColor(context), - fontWeight: FontWeight.w500, - ), - ), - ), - Expanded( - child: Text( - value, - style: TextStyle( - fontSize: 14, - color: AppColor.textStyleColor(context), - ), - ), - ), - ], - ), - ); + Color _getStatusTextColor(String status) { + switch (status.toLowerCase()) { + case 'approved': + return AppColor.greenStatusTextColor; + case 'rejected': + return AppColor.redStatusTextColor; + case 'pending': + default: + return AppColor.yellowStatusTextColor; + } } Widget _buildChip(String label) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 10.toScreenWidth, vertical: 4.toScreenHeight), - decoration: BoxDecoration( - color: AppColor.primary10.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColor.primary10.withValues(alpha: 0.2)), - ), - child: Text( - label, - style: TextStyle( - fontSize: 12, - color: AppColor.primary10, - fontWeight: FontWeight.w500, - ), + return Chip( + backgroundColor: AppColor.backgroundTabBarColor(context), + side: BorderSide(color: AppColor.border2Color(context)), + label: Text(label), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), + labelStyle: AppTextStyles.tinyFont2.copyWith( + color: AppColor.labelTextStyleColor(context), ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.symmetric(horizontal: 8.toScreenWidth, vertical: 4.toScreenHeight), ); } } diff --git a/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart b/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart index c1649652..f9884d92 100644 --- a/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart +++ b/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -185,12 +187,26 @@ class _MultipleItemDropDownMenuState