feat: enhance notification action handling and add refresh indicators in ServiceProjectDetailsScreen

This commit is contained in:
Vaibhav Surve 2025-11-22 16:17:17 +05:30
parent e9075dcdf5
commit 516d6b0489
2 changed files with 95 additions and 70 deletions

View File

@ -414,12 +414,17 @@ class NotificationActionHandler {
required String notFoundMessage,
required String successMessage,
}) {
if (!Get.isRegistered<T>()) {
_logger.w(notFoundMessage);
return;
}
try {
final controller = Get.find<T>();
onFound(controller);
_logger.i(successMessage);
} catch (e) {
_logger.w(notFoundMessage);
_logger.w('⚠️ Error updating controller: $e');
}
}
}

View File

@ -12,12 +12,14 @@ import 'package:on_field_work/helpers/widgets/avatar.dart';
import 'package:on_field_work/model/service_project/service_project_allocation_bottomsheet.dart';
import 'package:on_field_work/model/employees/employee_model.dart';
import 'package:on_field_work/view/service_project/jobs_tab.dart';
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
class ServiceProjectDetailsScreen extends StatefulWidget {
final String projectId;
final String? projectName;
const ServiceProjectDetailsScreen({super.key, required this.projectId , this.projectName});
const ServiceProjectDetailsScreen(
{super.key, required this.projectId, this.projectName});
@override
State<ServiceProjectDetailsScreen> createState() =>
@ -174,13 +176,20 @@ class _ServiceProjectDetailsScreenState
Widget _buildProfileTab() {
final project = controller.projectDetail.value;
if (project == null) {
return Center(child: MyText.bodyMedium("No project data"));
}
return Padding(
padding: MySpacing.all(12),
return MyRefreshIndicator(
onRefresh: () async {
await controller.fetchProjectDetail();
},
backgroundColor: Colors.indigo,
color: Colors.white,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: MySpacing.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -335,77 +344,85 @@ class _ServiceProjectDetailsScreenState
return Center(child: MyText.bodyMedium("No team members found"));
}
// Group team members by their role ID
// Group team members by role
final Map<String, List> roleGroups = {};
for (var team in controller.teamList) {
roleGroups.putIfAbsent(team.teamRole.id, () => []).add(team);
}
return ListView.separated(
padding: const EdgeInsets.all(12),
itemCount: roleGroups.keys.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final roleId = roleGroups.keys.elementAt(index);
final teamMembers = roleGroups[roleId]!;
final roleName = teamMembers.first.teamRole.name;
return Card(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 3,
shadowColor: Colors.black26,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Role header
MyText.bodyLarge(
roleName,
fontWeight: 700,
color: Colors.black87,
),
const Divider(height: 20, thickness: 1),
// List of team members inside this role card
...teamMembers.map((team) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Avatar(
firstName: team.employee.firstName,
lastName: team.employee.lastName,
size: 32,
imageUrl: (team.employee.photo?.isNotEmpty ?? false)
? team.employee.photo
: null,
),
MySpacing.width(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium(
"${team.employee.firstName} ${team.employee.lastName}",
fontWeight: 600,
),
MyText.bodySmall(
"Status: ${team.isActive ? 'Active' : 'Inactive'}",
color: Colors.grey[700],
),
],
),
),
],
),
);
}).toList(),
],
),
),
);
return MyRefreshIndicator(
onRefresh: () async {
await controller.fetchProjectTeams();
},
backgroundColor: Colors.indigo,
color: Colors.white,
child: ListView.separated(
padding: const EdgeInsets.all(12),
itemCount: roleGroups.keys.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final roleId = roleGroups.keys.elementAt(index);
final teamMembers = roleGroups[roleId]!;
final roleName = teamMembers.first.teamRole.name;
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
elevation: 3,
shadowColor: Colors.black26,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Role header
MyText.bodyLarge(
roleName,
fontWeight: 700,
color: Colors.black87,
),
const Divider(height: 20, thickness: 1),
// List of team members inside this role card
...teamMembers.map((team) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Avatar(
firstName: team.employee.firstName,
lastName: team.employee.lastName,
size: 32,
imageUrl:
(team.employee.photo?.isNotEmpty ?? false)
? team.employee.photo
: null,
),
MySpacing.width(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium(
"${team.employee.firstName} ${team.employee.lastName}",
fontWeight: 600,
),
MyText.bodySmall(
"Status: ${team.isActive ? 'Active' : 'Inactive'}",
color: Colors.grey[700],
),
],
),
),
],
),
);
}).toList(),
],
),
),
);
},
),
);
});
}
@ -457,7 +474,10 @@ class _ServiceProjectDetailsScreenState
controller: _tabController,
children: [
_buildProfileTab(),
JobsTab(scrollController: _jobScrollController, projectName: widget.projectName ?? '',),
JobsTab(
scrollController: _jobScrollController,
projectName: widget.projectName ?? '',
),
_buildTeamsTab(),
],
);