From 8ad2e79b1e920bb9d8c8c290dc285f6fb901173f Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:55:25 +0300 Subject: [PATCH] improvements --- .../models/site_request_model.dart | 30 ++- .../screens/site_request_form_screen.dart | 175 ++++++++++-------- .../screens/site_request_history_screen.dart | 122 ++++++++++-- 3 files changed, 228 insertions(+), 99 deletions(-) diff --git a/lib/modules/site_request_module/models/site_request_model.dart b/lib/modules/site_request_module/models/site_request_model.dart index 814f49ed..eb6236de 100644 --- a/lib/modules/site_request_module/models/site_request_model.dart +++ b/lib/modules/site_request_module/models/site_request_model.dart @@ -14,9 +14,11 @@ class SiteRequestModel { final List? oldSiteNames; // For Change Site request final List? departmentIds; final List? departmentNames; + final List? oldDepartmentIds; // For Change Site request - old departments + final List? oldDepartmentNames; // For Change Site request - old departments final String role; - final String status; // Pending, Approved, Rejected - final DateTime requestDate; + final String ?status; // Pending, Approved, Rejected + final DateTime? requestDate; SiteRequestModel({ this.id, @@ -32,9 +34,11 @@ class SiteRequestModel { this.oldSiteNames, this.departmentIds, this.departmentNames, + this.oldDepartmentIds, + this.oldDepartmentNames, required this.role, - required this.status, - required this.requestDate, + this.status, + this.requestDate, }); /// Converts model to JSON for API submission @@ -54,8 +58,8 @@ class SiteRequestModel { 'departmentIds': departmentIds, 'departmentNames': departmentNames, 'role': role, - 'status': status, - 'requestDate': requestDate.toIso8601String(), + // 'status': status, + // 'requestDate': requestDate?.toIso8601String(), }; } @@ -78,6 +82,8 @@ class SiteRequestModel { oldSiteNames: sitesData['oldSiteNames'], departmentIds: departmentsData['departmentIds'], departmentNames: departmentsData['departmentNames'], + oldDepartmentIds: departmentsData['oldDepartmentIds'], + oldDepartmentNames: departmentsData['oldDepartmentNames'], role: json['role'] ?? '', status: _parseStatus(json), requestDate: _parseDate(json), @@ -156,6 +162,8 @@ class SiteRequestModel { static Map?> _parseDepartmentsFromJson(Map json) { List? departmentIds; List? departmentNames; + List? oldDepartmentIds; + List? oldDepartmentNames; if (json['departments'] != null && json['departments'] is List && @@ -165,12 +173,22 @@ class SiteRequestModel { for (var dept in json['departments']) { departmentIds.add(dept['departmentId']?.toString() ?? ''); departmentNames.add(dept['departmentName'] ?? ''); + + // Check for old department data in Change Site requests + if (dept['oldDepartmentId'] != null && dept['oldDepartmentId'] != 0) { + oldDepartmentIds ??= []; + oldDepartmentNames ??= []; + oldDepartmentIds.add(dept['oldDepartmentId']?.toString() ?? ''); + oldDepartmentNames.add(dept['oldDepartmentName'] ?? ''); + } } } return { 'departmentIds': departmentIds, 'departmentNames': departmentNames, + 'oldDepartmentIds': oldDepartmentIds, + 'oldDepartmentNames': oldDepartmentNames, }; } 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 8bc635a5..cdf2108d 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 @@ -42,7 +42,7 @@ class _SiteRequestFormScreenState extends State { RequestTypeModel? _selectedRequestType; List _selectedSites = []; - List _selectedOldSites = []; // For Change Site - old sites + List _selectedOldSites = []; // For Change Site - old sites List _selectedDepartments = []; // Map to track replacements: oldSiteId -> {newSite, departments} @@ -99,69 +99,71 @@ class _SiteRequestFormScreenState extends State { return Scaffold( backgroundColor: AppColor.scaffoldBackground(context), appBar: const DefaultAppBar(title: "Site Request"), - body: _isLoadingData ?const ALoading().center:Column( - children: [ - Expanded( - child: Form( - key: _formKey, - child: SingleChildScrollView( - padding: EdgeInsets.all(16.toScreenWidth), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildUserInfoCard(), - 16.height, - _buildRequestTypeDropdown(), - 16.height, - if (_selectedRequestType != null) ...[ - if (_isChangeSiteRequest()) ...[ - _buildOldSiteDropdown(), - 16.height, - // Show replacement containers for each old site - if (_selectedOldSites.isNotEmpty) ...[ - Text( - "Site Replacements", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColor.textStyleColor(context), - ), - ), - 8.height, - ..._buildSiteReplacementContainers(), + body: _isLoadingData + ? const ALoading().center + : Column( + children: [ + Expanded( + child: Form( + key: _formKey, + child: SingleChildScrollView( + padding: EdgeInsets.all(16.toScreenWidth), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildUserInfoCard(), + 16.height, + _buildRequestTypeDropdown(), + 16.height, + if (_selectedRequestType != null) ...[ + if (_isChangeSiteRequest()) ...[ + _buildOldSiteDropdown(), + 16.height, + // Show replacement containers for each old site + if (_selectedOldSites.isNotEmpty) ...[ + Text( + "Site Replacements", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColor.textStyleColor(context), + ), + ), + 8.height, + ..._buildSiteReplacementContainers(), + ], + ] else if (_isRemoveSiteRequest()) ...[ + _buildRemoveSiteDropdown(), + ] else ...[ + _buildSiteMultiSelect(), + 16.height, + _buildDepartmentMultiSelect(), + ], + ], ], - ] else if (_isRemoveSiteRequest()) ...[ - _buildRemoveSiteDropdown(), - ] else ...[ - _buildSiteMultiSelect(), - 16.height, - _buildDepartmentMultiSelect(), - ], - ], - ], + ), + ), + ), ), - ), - ), - ), - if (_selectedRequestType != null) - Container( - padding: EdgeInsets.all(16.toScreenWidth), - decoration: BoxDecoration( - color: AppColor.background(context), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 10, - offset: const Offset(0, -2), + if (_selectedRequestType != null) + Container( + padding: EdgeInsets.all(16.toScreenWidth), + decoration: BoxDecoration( + color: AppColor.background(context), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, -2), + ), + ], + ), + child: SafeArea( + child: _buildSubmitButton(), + ), ), - ], - ), - child: SafeArea( - child: _buildSubmitButton(), - ), + ], ), - ], - ), ); } @@ -242,11 +244,13 @@ class _SiteRequestFormScreenState extends State { Widget _buildOldSiteDropdown() { return MultipleItemDropDownMenu( context: context, - title: "Old Site (Current Site)", - initialValue: _selectedOldSites.map((s) => SiteRequestSiteModel( - id: s.identifier ?? '', - name: s.name ?? '', - )).toList(), + title: "Current Sites", + initialValue: _selectedOldSites + .map((s) => SiteRequestSiteModel( + id: s.identifier ?? '', + name: s.name ?? '', + )) + .toList(), staticData: _provider.userCurrentSites, showAsBottomSheet: true, backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, @@ -288,10 +292,12 @@ class _SiteRequestFormScreenState extends State { return MultipleItemDropDownMenu( context: context, title: "Site to Remove", - initialValue: _selectedSites.map((s) => SiteRequestSiteModel( - id: s.identifier ?? '', - name: s.name ?? '', - )).toList(), + initialValue: _selectedSites + .map((s) => SiteRequestSiteModel( + id: s.identifier ?? '', + name: s.name ?? '', + )) + .toList(), staticData: _provider.userCurrentSites, showAsBottomSheet: true, backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, @@ -384,20 +390,29 @@ class _SiteRequestFormScreenState extends State { return Consumer( builder: (context, siteProvider, child) { - // Filter out already selected new sites and the current old site + // Get ALL user's current site IDs to exclude from new site selection + final allUserCurrentSiteIds = _provider.userCurrentSites + .map((s) => s.identifier ?? '') + .toSet(); + + // Filter out already selected new sites and ALL user's current sites final alreadySelectedNewSites = _siteReplacements.values .where((r) => r.newSite != null) .map((r) => r.newSite!.identifier) .toSet(); final filteredSites = siteProvider.items.where((site) { - // Exclude the old site itself - if (site.identifier == oldSiteId) return false; + final siteId = site.identifier ?? ''; + + // Exclude ALL user's current sites (not just selected old sites) + if (allUserCurrentSiteIds.contains(siteId)) return false; + // Exclude already selected new sites (except current one) - if (replacement?.newSite?.identifier != site.identifier && - alreadySelectedNewSites.contains(site.identifier)) { + if (replacement?.newSite?.identifier != siteId && + alreadySelectedNewSites.contains(siteId)) { return false; } + return true; }).toList(); @@ -599,8 +614,8 @@ class _SiteRequestFormScreenState extends State { departmentIds: departmentIds.isNotEmpty ? departmentIds : null, departmentNames: departmentNames.isNotEmpty ? departmentNames : null, role: _provider.getCurrentUserRole(_userProvider), - status: 'Pending', - requestDate: DateTime.now(), + // status: 'Pending', + // requestDate: DateTime.now(), ); final success = await _provider.submitSiteRequest(request, _userProvider); @@ -647,8 +662,8 @@ class _SiteRequestFormScreenState extends State { departmentIds: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.identifier ?? '').toList() : null, departmentNames: _selectedDepartments.isNotEmpty ? _selectedDepartments.map((d) => d.name ?? '').toList() : null, role: _provider.getCurrentUserRole(_userProvider), - status: 'Pending', - requestDate: DateTime.now(), + // status: 'Pending', + // requestDate: DateTime.now(), ); final success = await _provider.submitSiteRequest(request, _userProvider); @@ -663,7 +678,9 @@ class _SiteRequestFormScreenState extends State { // API will handle success/error toast messages if (success && mounted) { - Navigator.pushReplacementNamed(context, SiteRequestHistoryScreen.routeName); + UserProvider userProvider = Provider.of(context, listen: false); + await _provider.fetchSiteRequests(pageNumber: 1, pageSize: 10, userProvider: userProvider); + Navigator.pop(context); } } } 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 d3c6f5b7..63e9fc21 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 @@ -67,7 +67,13 @@ class _SiteRequestHistoryScreenState extends State { } Future _refreshData() async { + setState(() { + _isLoadingData = true; + }); await _provider.refreshSiteRequests(userProvider: _userProvider); + if (mounted) { + setState(() => _isLoadingData = false); + } } @override @@ -149,21 +155,42 @@ class _SiteRequestHistoryScreenState extends State { _buildRequestHeader(request), InfoHeaderWidget("Site Request #${request.id ?? 'N/A'}"), _buildUserInfo(request), - _buildSitesSection(request), - if (request.departmentNames != null && request.departmentNames!.isNotEmpty) + // Show old sites if present (for Change Site requests) + if (request.oldSiteNames != null && request.oldSiteNames!.isNotEmpty) + _buildOldSitesSection(request), + // Show new/current sites only if they are not empty and not all null + if (_hasValidSiteNames(request.siteNames)) + _buildSitesSection(request), + // Show old departments if present (for Change Site requests) + if (request.oldDepartmentNames != null && request.oldDepartmentNames!.isNotEmpty) + _buildOldDepartmentsSection(request), + // Show new departments only if they are not empty and not all null + if (_hasValidDepartmentNames(request.departmentNames)) _buildDepartmentsSection(request), ], ).toShadowContainer(context).paddingOnly(bottom: 16); } + // Helper method to check if site names list has valid (non-null, non-empty) values + bool _hasValidSiteNames(List siteNames) { + return siteNames.isNotEmpty && siteNames.any((name) => name.isNotEmpty); + } + + // Helper method to check if department names list has valid (non-null, non-empty) values + bool _hasValidDepartmentNames(List? departmentNames) { + return departmentNames != null && + departmentNames.isNotEmpty && + departmentNames.any((name) => name.isNotEmpty); + } + Widget _buildRequestHeader(SiteRequestModel request) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ StatusLabel( label: request.status, - textColor: _getStatusTextColor(request.status), - backgroundColor: _getStatusColor(request.status), + textColor: _getStatusTextColor(request.status ?? ''), + backgroundColor: _getStatusColor(request.status ?? ''), ), 8.width, StatusLabel( @@ -185,19 +212,70 @@ class _SiteRequestHistoryScreenState extends State { 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), + if (request.extensionNumber != null && request.extensionNumber!.isNotEmpty) InfoTextWidget(label: "Extension", value: request.extensionNumber!, showEmptyValue: true), InfoTextWidget(label: "Role", value: request.role), ], ); } + Widget _buildOldSitesSection(SiteRequestModel request) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Old Sites:", + style: TextStyle( + fontSize: 14, + color: AppColor.labelTextStyleColor(context), + fontWeight: FontWeight.w500, + ), + ), + 8.height, + Wrap( + runSpacing: 4.toScreenHeight, + spacing: 4.toScreenWidth, + children: request.oldSiteNames!.map((site) => _buildChip(site, isOld: true)).toList(), + ), + ], + ).paddingOnly(bottom: 8); + } + Widget _buildSitesSection(SiteRequestModel request) { + // Determine label based on whether old sites exist + final label = (request.oldSiteNames != null && request.oldSiteNames!.isNotEmpty) + ? "New Sites:" + : "Sites:"; + + // Filter out null or empty site names + final validSiteNames = request.siteNames.where((site) => site.isNotEmpty).toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 14, + color: AppColor.labelTextStyleColor(context), + fontWeight: FontWeight.w500, + ), + ), + 8.height, + Wrap( + runSpacing: 4.toScreenHeight, + spacing: 4.toScreenWidth, + children: validSiteNames.map((site) => _buildChip(site)).toList(), + ), + ], + ).paddingOnly(bottom: 8); + } + + Widget _buildOldDepartmentsSection(SiteRequestModel request) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Sites:", + "Old Departments:", style: TextStyle( fontSize: 14, color: AppColor.labelTextStyleColor(context), @@ -208,18 +286,26 @@ class _SiteRequestHistoryScreenState extends State { Wrap( runSpacing: 4.toScreenHeight, spacing: 4.toScreenWidth, - children: request.siteNames.map((site) => _buildChip(site)).toList(), + children: request.oldDepartmentNames!.map((dept) => _buildChip(dept, isOld: true)).toList(), ), ], ).paddingOnly(bottom: 8); } Widget _buildDepartmentsSection(SiteRequestModel request) { + // Determine label based on whether old departments exist + final label = (request.oldDepartmentNames != null && request.oldDepartmentNames!.isNotEmpty) + ? "New Departments:" + : "Departments:"; + + // Filter out null or empty department names + final validDepartmentNames = request.departmentNames!.where((dept) => dept.isNotEmpty).toList(); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Departments:", + label, style: TextStyle( fontSize: 14, color: AppColor.labelTextStyleColor(context), @@ -230,20 +316,28 @@ class _SiteRequestHistoryScreenState extends State { Wrap( runSpacing: 4.toScreenHeight, spacing: 4.toScreenWidth, - children: request.departmentNames!.map((dept) => _buildChip(dept)).toList(), + children: validDepartmentNames.map((dept) => _buildChip(dept)).toList(), ), ], ); } - Widget _buildChip(String label) { + Widget _buildChip(String label, {bool isOld = false}) { return Chip( - backgroundColor: AppColor.backgroundTabBarColor(context), - side: BorderSide(color: AppColor.border2Color(context)), + backgroundColor: isOld + ? AppColor.redStatusColor.withValues(alpha: 0.1) + : AppColor.backgroundTabBarColor(context), + side: BorderSide( + color: isOld + ? AppColor.redStatusColor.withValues(alpha: 0.3) + : AppColor.border2Color(context), + ), label: Text(label), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), labelStyle: AppTextStyles.tinyFont2.copyWith( - color: AppColor.labelTextStyleColor(context), + color: isOld + ? AppColor.redStatusTextColor + : AppColor.labelTextStyleColor(context), ), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, padding: EdgeInsets.symmetric(horizontal: 8.toScreenWidth, vertical: 4.toScreenHeight),